If Ada is already *very* safe, why Rust?

One of the first things I tried to find out about Ada is how it manages dynamic resources. What I found out is:

  1. Optional GC, on which no one can depend reliably due to its optionality
  2. Manual new/Unchecked_Deallocation
  3. RAII pointers either don’t exist or are buried deep in manuals
  4. Finally, pointers (“access types”) are nullable by default.

How can Ada be called safe language then??? Even dreaded C++ does the job better. Not to mention Haskell and friends which make as many invalid states as possible simply inexpressible.

1 Like

[mod note: Specific comparison of Rust/Ada trade-offs is fine, but general critique of Ada is off topic here.]

3 Likes

if only Ada would have ever had Rust’s community! I come from a Pascal background originally and wouldn’t have minded that.

Well… Ada has a very active community, one of my preferred channels is a google group
https://groups.google.com/forum/#!forum/comp.lang.ada
Take a look if you are curious about it, and welcome.

One thing I would point out, is that, according to https://blog.rust-lang.org/2018/11/27/Rust-survey-2018.html, Rust has only 9.6% of users with experience longer than 3 years. In Ada community it’s quite common to find people with far 20 years, some even with 30 yeas of experiencie, still active. I might say my 12 Ada years would put me in a baby chair. A high number of new people is sure awesome, but a good number of more experienced people tells a lot about the maturity of the community and helps a lot in the more complex discussions.

But the lack of popularity of Ada—at least in open source development—makes that bindings are usually badly maintained. Unless you’re working on something that is entirely built from scratch that’s going to give you a lot more work.

I would not say so. Ada is very popular among military systems, and it’s been widely used in tons of critical systems. For open source projects you could say it is not as popular as Rust, but it is changing. Still there are some initiatives to turn more popular for these projects, eg, the hackster Make with Ada contest

Also, according to TIOBE index, Ada is almost as popular in use as Rust, in the 36th position where Rust is in 35th, with the same score.
https://www.tiobe.com/tiobe-index/

Besides formal work, I personally have also used Ada even for my private and public projects. No problem with other languages, but Ada’s way for me is very intuitive (for the type of project I like to do for myself), so I just have to think in the system as the language flows. Bindings are also quite easy, when I don’t want to implement from scratch, no problem in using a C/C++ code.

2 Likes

Link?

It doesn’t have to be better or worse. For safety critical applications Ada is much mature language and I’m not sure Rust will support Ada Spark’s code contract and verifying software with specs features (although I do like to see it in Rust too).

For me personally I can say:

  1. Syntax. I prefered if Ada/Spark was based on C syntax.

    Rust is closer to C syntax and much less verbose.

  2. Ada/Spark does not have ownership rules and borrow checker build into the compiler like Rust (already mentioned earlier). I think they are adding this to Ada/Spark 2020.

  3. It is GPL license for community edition. If you want to keep your code closed, you have to buy commercial license which is very expensive for small developer.

    Rust is free but I don’t mind paying in the range of Visual Studio Professional for Rust and pro tools if it had.

Not true.

Not Ada/Spark limitation. It is guideline/requirement for developing avionics software. Developers using C language have to follow this for avionics software too.

In my opinion, choices between languages are complicated for each developer and even with very similar features a developer might use one language over the other.

5 Likes

Ada and Rust had different, but overlapping, goals. They are each a product of the best computer language knowledge and compiler technology of their times. Both languages continue to evolve, to some extent even cross-fertilizing each other.

I was a minor participant in the original design of Honeywell’s Green submission, which became Ada, so I have some knowledge whereof I speak.

Edit: To cause the link to navigate to the appropriate credits paragraph.

15 Likes

Just a minor correction here. There is a free commercial friendly Ada compiler as well. The FSF version of GNAT provides an exception to the standard GPL allowing you to link to any of the runtime files without having that make your code or binary fall under the GPL. Alternately if you feel froggy (and are more experienced), you can also create your own runtime under your own license and still use the community edition. It's the runtime that makes a difference. The FSF version, however, is free and pretty easy to get for the majority of the platforms that support GCC (Windows, Linux, Mac, etc.).

They have GNAT Pro Developer license for 3 or less developers which some articles say is reasonable for small developers but it doesn’t publish the price. They should advertise the price, it may get more people interested.

You've got to love it when people who nothing about Ada like to tell other people, who know nothing about Ada, things about Ada that they probably heard from someone else, who knew nothing about Ada.

Ada has a new and it has a "free" but free is considered unsafe and is a generic function called Unchecked_Deallocation, because it's unchecked by the compiler.

You would generally wrap your memory allocations within a package inside the private parts either in functions to call from outside the package or using a controlled type, which is a tagged type (or class in other languages) which have Initialize, Adjust and Finalize functions defined which you need to override. Handle your free within Finalize and you essentially have RAII (in other languages). Object goes out of scope and Finalize is called.

Also, there are provisions within the RM which states that when an access type (pointer type) goes out of scope, i.e. from a block, the block master can automatically delete that memory for you. This thread goes into more detail (I cannot put the link in):

Specify the 'Storage_Size associated with the access-type.
The implementation is required to reclaim the storage for the
access type when exiting its scope if a 'Storage_Size is specified
(see RM95 13.11(18)).

One of Ada's best features are ranges, along with array indices:

type Celcius is range -100 .. 100;

type Celcius_Arrays is array (Ceclius'Range) of Integer; -- Example.
...
C : Celcius_Arrays := <some_initialiser>;
...
for C in C'Range loop - Yes, an array from -100 -> 100
  -- Do something
end loop;

[As I cannot post another reply...]

There are no compilers ever written for Ada which implemented the provision for GC.

Not quite true, see my previous post.

type A is access T; -- Can point to local types.
type B is access all T; -- Can point to types on the heap and locally.
type C is not null access T; -- Exclude nulls.
type D is access constant T; -- Can only point to constants.
type D is not null access constant T; -- Can only point to constants and not null.

Then there are access to (protected) functions/procedures, etc.

It's a hell of a lot safer than a lot of other languages out there including C++, which doesn't do the job better.

8 Likes

Remember, Ada was created to replace the thousands of languages already in use at the DoD, so was intended as a general purpose programming language.

[As I cannot post another reply]

Ada 202x has parallel blocks for fine grained concurrency:

parallel for I in A'Range loop
end loop;

-- and

parallel begin
   -- Statrements ex execute in parallel with
and 
   -- These statements
and 
   -- These statements
and 
   -- These statements
ane
   -- etc.
end;

If that were true, we wouldn't have lost so many people who just got sick of the lack of movement.

Talking from experience here, I created the SDL2 bindings, which are still in development. You can either do a really thin binding which doesn't utilise any Ada feature and it's just like programming in C, but in Ada. Or you go the thick route, which imports the functions and sometimes the structures, depending, and then wraps them in Ada types which proper ranges. The difficulty here is that for a lot C libs, there are no ranges defined or even thought of. Thicker bindings takes substantially longer due to this, which in turns makes it difficult to keep going. So, yeah, you'll find bindings in sometimes ok shape and sometimes not bad and sometimes just bad.

3 Likes

One major difference is that Ada was created at a time when most military computers were single-core with in-order sequential execution and no cache. Although limited SIMD existed, any other concurrency was very coarse-grained. That’s the underlying execution model for C, Pascal, C++, etc.

Rust was created to address the complexity of multi-core processors with multi-level cache hierarchies where computational efficiency may require much concurrency. In my experience few humans are capable of error-free design and implementation of highly-concurrent systems unless they employ tooling that flags their errors in conceptualization or implementation.

5 Likes

This is a frustration when talking about Rust outside the Rust community, too.

6 Likes

Well, that makes things much nicer. It’s unfortunate though that this info is buried somewhere in manuals, and the default behavior is to create non-owned access type. It’s easier to make mistake than to write correct code then. Although does this feature support moving such heap-allocated storage out of current scope?

EDIT

I tried to skim through storage pool management and unfortunately couldn’t grasp idea how to properly use 'Size clause. Could you please provide example analogous to

struct Foo(u32);
...
let foo_box = Box::new(Foo(42u32));

?

My point is, it’s explicit “not null” instead of explicit “nullable”. Less safer behavior is the default one.

std::unique_ptr is one of the first things which are taught to C++ newcomers. While knowing about ADA’s Storage_Size requires sifting through manuals or forum threads.

I agree and the default should be to deallocate on the access type going out of scope on end;.

No, because I don't know what that is doing.

But the Ada equivalent would be use the representation clause:

type Some_Ptr is access ...;
for Some_Ptr'Storage_Size use <some_size>;

or the aspect, which Ada 2012:

type Some_Ptr is access ... with
  Storage_Size => <some_size>;

Yes, but they cannot make that the default because of legacy.

Starting Ada programmers don't even use new/Unchecked_Deallocation at teh start, because most things are statically allocated either in packages or on the stack, so it's only when you need to do container type stuff that you require allocators at all. Allocators are considered to be a more advanced Ada feature. It's possible to write entire programs without using them at all. You could say that's even safer than Rust in that regard.

You can write smart pointers in Ada using controlled types with generics, as I mentioned before.

2 Likes

Nothing exceptional here, Rust no_std crates successfully demonstrate possibility of writing programs without allocators as well. But I fail to see how presenting allocations as an “advanced feature” helps promoting Ada as a modern general purpose language.

Can you elaborate? Specifically in comparison with Rust no_std programs.

1 Like

No allocations = no leaks.

I meant that if your data is static and constant and is built into a package, you can run as many tasks as you like without any locking mechanisms.

Obviously if there is mutable data in those tables, that will need to be locked, obviously.

1 Like

Unfortunately apps with statically known data set are not very common, to say the least.

In Ada you can have dynamically sized variables or objects on the stack, thanks to unconstrained types. No pointers at all, no heap allocation.

For complex cases like trees, linked lists, etc. you can use the Ada.Containers which have deterministic (then predictable) garbage collection via finalization.

Are you really looking at Ada through a 'C++ lens'? That is one of the main point of Lucretia:

  • Ada can moves you miles before even needing an equivalent to 'std::unique_ptr'. There is your main constraint on newcomers and a sound app architecture. Need to make a ninja 'move'?... check the manual. Stop comparing to 'how C++ would do'. Doing so, you blind yourself to what Ada has to offer.

Ada has all the control you may need... but you have to ask for it. The fact that Ada is build around explicit mechanism is godsend in regard to all aspects of software engineering. I respect you like and appreciate Rust but I fail to understand how the requiring of such implicit knowledge to achieve so little is desirable in any production environment?

In contrast, Ada is a very straight shooter; its focus is on the result, not the act of. This is probably what makes it so good at producing reliable abstractions... like you know ... your surgical robot software or ... your avionic navigation system.

Ada delivered such system for the past 40 years so yeah, Rust has ground to cover in that respect.

Anyway, try it.

1 Like

Ada's mentality is derived from the working definition of type as a set of values and a set of operations on those values and subtype as an additional constraint (possibly null) on the set of values — Thus we can say:

Integer_Size : Constant := 32;
Type Integer is range -2**(Integer_Size-1)..2**(Integer_Size-1)-1
    with Size => Integer_Size;
Subtype Natural is Integer range 0..Intger'Last;
Subtype Positive is Natural range Natural'Succ(Natural'First)..Natural'Last;

As you can see, this is exactly like defining a set, and then making useful subsets by adding more constraints, restricting the possible values. (This is very useful in terms of reasoning; it's rather a shame that the OOP-hype of the past few decades has produced a sort of "it's not useful if you can't extend it" mentality, which is easily disproven by recursion.) — Anyway, access-types are essentially the same: the normal values being [null | <reference>] and the addition of the constraint (not null) reduces that to [<reference>].

EDIT: Note that everything in the above example is in terms of the named-number Integer_Size; this means that if you had a 48-bit machine all you would have to do is change the constant to 48. (Or 64 for a 64-bit machine.)

3 Likes