Irenic language comparisons: Ada and Rust, AoC overall

Ada and Spark are not the same, and the writeup is not about Spark, even if I mention it a few times, and give Spark credit for quite a bit. If you think it’s insufficient, I’d be grateful for a concrete suggestions.

1 Like

I can’t speak to what they do, though again I’d be curious about the domain they’re working in. I can only write about my own experience, or consider the papers you’ve presented, which I’ve done.

Surely I misunderstand what you’re writing. As I mentioned in the writeup, Rust encourages stack-based programming. It often uses references, yes, but that’s not necessarily the same – after all, Ada does the same, and you don’t seem to think Ada has pointers to object everywhere, and seem to be saying at the end that Rust doesn’t encourage stack-based programming.

To the contrary, the papers you’ve cited to strikes me as making some unusual choices for libraries and the like. That doesn’t make them bad studies; I’d just be more curious about a broader survey of the most-used packages on crates.io, say.

For instance, the paper studies Servo, a web browser. That’s not what I’d call a typical example of an application. (I’m not aware of any web browsers written in Ada.) Downloading and using it, it’s clearly not ready for prime time. (By this I mean that it ran fine – no crashes, etc. – but displays pages very badly.) Even so, in hundreds of thousands of lines of code, I found about 100 instances of the word “unsafe”, most of which isn’t even usage of unsafe. Many genuine uses of unsafe were to interact with C, which is unavoidable.

It’s one thing for someone to use unsafe because one has to in an extraordinary or unusual circumstance, and another altogether to use it routinely because it’s less inconvenient than doing the right thing.

And again, based on the code I’ve looked at – not just our project – use of unsafe is extremely rare.

or

with some screenshots :slight_smile:

3 Likes

No readme, no INSTALL… But seeing the .cmd file I reckon that this is windows-only. Secure browser only the most insecure OS. Makes sense. You’re also seriously curtailing your number of beta (well, alpha) testers. Usually people who worry about security choose better oses for that very reason.

Then I don’t understand all the fuss about Rust’s ownership/borrowing rules and their associated memory safety. A low-level language that has pointers to objects everywhere, with manual allocation and deallocation, such as C, has memory-safety issues. If you create a similar language, but with Rust’s safety rules, then you have a much safer low-level language with pointers to objects everywhere, and if you think such languages are a good idea, that’s a big deal. But if you create a stack-based language with pointers to objects rarely needed, then it doesn’t seem worth the effort and restrictions, and hardly worth making a fuss about.

I’m not sure what you mean by “references”. AFAIK, I’ve never used one in Ada.

Interesting. As mentioned above, the projects in the paper total about 800 KLOC and contained nearly 5000 unsafe instances. Servo is listed as 271 KLOC, so one would expect it to have about 1620 unsafe instances. Perhaps the project has improved in the years since the paper (perhaps due to the paper). The 2024 paper lists the same sizes for the projects, so presumably it is using the same snapshots of the projects. Not sure about the other 12 projects it applies its checkers to.

Not really.
Linux is more insecure due to several factors:

  1. The everything is text & “bag of bytes” design-philosophy.
    1. The “small tools” idea encourages “scripting” (on the commandline) and the use of pipes to glue “small tools” together.
    2. Which discards type-information at every step in the process,
    3. which forces ad hoc parsing at every step, based on assumptions of the output (e.g. “I’ve never seen column 2 give a zero/negative-number, so I’ll just assume that it’s always positive.”)
  2. The user-hostility (esp. cryptic command) gives a sense of superiority, the “hidden knowledge”, which really is the lack of any real consistency driven by the design above —illustrated by counter-example, OpenVMS’s structured commandline gives a form and typing to inputs of parameters and, IIRC, is handled at the OS-library/-interface level, so any program using it gets that consistancy— or, otherwise known as “security through obscurity”
  3. The interplay between the host-language and the OS, especially with the genesis of design: that is to say, because C and Unix “grew up together”, sharing the same design-philosophies, they exhibit similar weaknesses; for example, the “well, a good programmer wouldn’t X” while simultaneously making X the default/easy thing to do. (e.g. not checking the result of malloc, or fopen.) — While Windows does inherit some of this from C, there was a break in the “design feedback-loop” which prevented it from becoming quite as prevelant.
1 Like

Huh… It’s my fault egging you one so sorry. I could certainly respond in kind but this is not the place. I suffices to count the number of viruses Windows get even know, and ask yourself if most of these attacks even be attempted on UNIX, if the OS market shares were reversed.
But I agree on the command line paradigm needing a serious overhaul. But you may blame the userland more than the system. You could develop a new terminal emulator and shell interpreter with strong typing in Ada. Issue is no one cares enough about fundamental issues. But realistically things overall are worse everywhere else so you’re being very misleading and biased.
Let us stop here about OSes…

Look, I’m not saying Windows is great, not at all.
But the fact is that the first virus, a worm, was a Unix program.
The actually more secure OSes like OpenVMS aren’t really around today; and Multics was so robust that you could e.g. replace ram while in operation.

Sorry, but I got really, REALLY tired of always being told “just download linux and use that” when I was in my university’s CS program when I made known my interest in building my own OS. — All blithely superior, not considering the implications of the underlying designs. (Just like the smug “use C” that you’d sometimes get from programmers making the assumption that C=efficient.)

1 Like

I guess it depends on if you ever iterated over a container in Ada or not using a for loop with the of keyword. If so, then you’ve implicitly used a generalized reference: User-Defined References

1 Like

Replying out of order.

While you may not have meant this, it seems opportune to mention that a lot of neophytes look at Rust code, see the unfortunate*** & operator strewn about, and conclude (wrongly) that it’s slinging pointers around, or at least references. That’s not what & means. It doesn’t even necessarily mean a reference. For example:

if (5..10).contains(&3)

…is not pointing to, or referencing 3. It’s “borrowing” it.

***Unfortunate for many reasons, all IMHO of course. For example:

  • While I don’t know this, I’m quite sure it descends from C’s “address-of” operator, so people naturally think it’s a pointer.
  • Or they might think of C++'s “reference-to” operator, and think it’s a pointer.
  • Either way, as the .contains method shows, you are not taking the address of 5. You are “borrowing” the 5.
  • And that illustrates why I think Rust’s designers were mistaken to make move semantics the default, instead of borrow semantics, which I’ve always found more intuitive, as did my students. (I can give examples if anyone wants to go down that :rabbit: :hole:.)

I’m not sure what you mean by “references”. AFAIK, I’ve never used one in Ada.

@jere has already mentioned one example where the Ada standard library explicitly has Reference or a Constant_Reference. I’m pretty sure another occurs whenever you pass a large record or array type as a parameters. From your point of view, you are simply specifying in or out or in out. But the compiler still has to decide whether to copy that 2GB matrix (to take an example from my past life) or merely pass the pointer underneath. I’d be very surprised if the compiler slings that much data around.

(If a 2GB matrix seems unrealistic to you, substitute a 40MB photo.)

In both Rust and (I believe) Ada, most of that data will not be on the stack (especially if you use a vector), but the only variable you interact with will be on the stack. At some point, the heap memory has to be freed. With Ada, that occurs with end of scope. With Rust, that occurs when the final variable that owns the data is last used, which can be when the variable goes out of scope, or sooner; e.g., an explicit drop (almost always unneeded), or the compiler’s detecting when you last need it.

And of course, Ada doesn’t automatically detect and release user-specified allocations. Rust does. Those may be rare, but they do occur. The Ada programmer typically has to track them and Unchecked_Deallocation them; the Rust programmer typically doesn’t.

Rust’s ownership/borrowing rules aren’t just about dangling pointers (say); they’re also about thread safety. Take that 2 GB matrix – no, rather, take a fairly small structure, one that unquestionably sits on the stack, pass it to several different procedures running in concurrent processes, some of which want to mutate it; some of which want to “move” the data (e.g., copy and overwrite); etc. The rules disallow that from happening.

Added after the fact: If it helps, AdaCore’s learnada.com gives several examples of when Ada programmers may be passing by reference.

There’s “sparforte” - it didn’t want to build on macOS (looking for libgnat under /usr/lib? I think not), so I’ll carry on with bash.

The Rarional R1000’s scripting language was actually Ada, or at any rate so close you’d have to squint hard to see differences.

3 Likes

You can also use HAC for bash-like jobs. An example:

Good point (about the README) → fixed.
You will find there the short list of targets (not only Windows)…

1 Like

I wouldn’t think a numeric literal would have an address, or need borrowing. It’s not like it can be changed (though there was a [probably apocryphal] story about someone who changed the value of a numeric literal for all Fortran programs on a CDC 6400).

Those are anonymous access types, which I won’t touch with a 3-m pole.

The parameter-passing mechanism is an implementation detail that is rarely of any interest.

What happens to that 2-GB matrix, and when, depends on how you create it. I’d use a custom holder, since the standard holder doesn’t seem to have any way to get data into it except by copying; see Image_IO.Holders for an example. I haven’t used it for anything bigger than a 10-MP image (so 30 MB), but it shouldn’t have any trouble with 2 GB.

According to your article, Rust doesn’t have concurrency, which surprised me, since Rust claims to prevent data races. Data races have been observed in safe Rust, and I presume that avoiding them is no more difficult than ensuring that Ada tasks don’t access any global variables except protected objects.

Careful. The icon used in the table indicates that Rust requires you to select a library for concurrency, but at least two libraries are available (tokio, mentioned in the article, and embassy, unmentioned) and I know tokio is widely used. Both async and .await are part of the language itself.

What do they do if you’re not using a concurrency library?

Compile-time error, because you need an executor. You can only call async functions from an asynchronous context, which means you have to get into an asynchronous context somehow. Once you select a library, you also add an annotation somewhere that enables the asynchronous mechanisms.

For example,

  • With tokio you annotate the main function with #[tokio::main].
  • With futures you use an executor from its executors module.
  • With embassy you annotate the main function with #[embassy_executor::main].
  • With async-std you use its task module.

I should clarify that when I’m talking about concurrency here, I’m talking about application-maintained concurrency; e.g., threadpools. Since none of the Rust reviewers has complained so far (one even had past experience with Ada), I’m working from the assumption that this is generally what Rust users think of.

However, someone reminded me that you can spawn OS-maintained threads in Rust without going through async, and in that case you don’t need a separate library. (I’ve verified this with an example.)

My impression is that this is another area where Ada relieves the user of worrying about it, but this is why I have that disclaimer section about not perhaps being the best tour guide, so let me know if I’m completely off base. I’ve used async and .await but I’m definitely not an expert on them.

1 Like

ThePrimeTime channel on youtube says async and await are truly painful in Rust.

1 Like

Do they say why?

I wouldn’t say they’re truly painful, but it’s a bit like lifetimes; the learning curve is steep, and hits you very quickly the moment you need them. It says something when the reference for async-std feels it has to say, “It really isn’t that hard!”.

He didn’t elaborate but he spent two years using Rust and writing tools etc. at Netflix. It was a casual comment on a non Rust video. He has some Rust specific videos so I will let you know if I find out more details. He is now using Go and says he loved Rusts power such as traits but loves Gos simplicity. Ada might be the language he is looking for 🤷🏼‍♂

2 Likes