web analytics

Should I choose Ada, SPARK, or Rust over C/C++?

Please read the AdaCore blog written by Quentin Ochem.

Ada Rust SPARK
Community Small Large Small
Toolchain Embedded Ecosystem Mature In development Mature
Certification Off-the-shelf In development Off-the-shelf
Libraries available Limited Large Limited
Programming paradigm Imperative system-level Imperative system-level Imperative system-level
Mitigation of programming errors Yes Yes Yes
Strong Typing Yes Limited Yes
Data constraints, hardware / software data consistency Yes No Yes
Guaranteed absence of run-time errors Run-time checks Run-time checks Static, via Proof
Contract language (pre- post- conditions, invariants, predicates…) Yes, checked at run-time No Yes, checked statically, via proof
Memory safety

Pointer avoidance

Accessibility checks

Dynamic checks

Borrow checker

Lifetimes

Borrow checker

Pointer avoidance

Accessibility checks

Cost of adoption Language change Language change

Methodology change

Expected benefits

Mitigation of programming errors

Constraint checks

Mitigation of programming errors

Memory safety

Mitigation of programming errors

Memory Safety

Guarantee of absence of run-time errors

Guarantee of formal properties

Guarantee of constraint checks

Testing reduction


Rust vs Ada. How do they compare?

From Reddit r/rust discussions by u/rad_pepper

I worked with both Ada and with Rust quite a bit. There's a lot of features that both have, but Rust comes out ahead as being a "safe" language. Ada is not fully memory safe. Yes, you can corrupt memory in Ada, and even have dangling pointers but it's much harder to do than in C.

Rust constricts all the scary ops to unsafe blocks where it's expected that you know what you're doing and you make a safe interface to hook it into the rest of your code. You're not allowed to just go play around in memory and store pointers to random spots in memory and hop around like a frog with pointer arithmetic in normal Rust code.

Arrays

Both Ada and Rust provide bounds checking of arrays. Ada's built-in strings aren't actually null-terminated, they're just a bounds checked array of Character.

Ada allows returning variable-length arrays out of functions since arrays carry their own bounds. This means a lot of times you won't heap allocate where you otherwise would in other languages, and there's a lot fewer sharp corners because your indices get checked. There's a built-in Storage_Error, which gets raised in this case on stack overflow.

Pointers

Ada doesn't have plain pointers, instead there's these other things called access types, which are a form of typed pointer. Access types obey a concept called "accessibility" (unrelated to a11y) which only allows them to only point to variables which live as long as they do, in a similar, but less overall complete way than how borrowing works. This prevents pointing to something in a scope which can disappear on you.

In addition, accesses also get checked at runtime to see if they are null, this penalty can be removed if marked as not null (sort of like a C++ reference) or it's known to always have a value. You cannot perform pointer arithmetic on access types.

If you really want to do this, you can dive into Interfaces.C and convert to a system address if you want, but that's usually a red flag.

Each access type has its own storage pool (allocator) and you allocate using new to grab something and Unchecked_Deallocation to free it, which also nulls out the access (unlike C). Freeing an object and then accessing it again will trigger a runtime exception, as will running out of memory when trying to allocate. This system works pretty well in general for dealing with dynamic allocation.

Rust uses referenced-counted types, Rc and Arc, as well as a move-only Box for this. Since Ada has it's own version of drop (i.e. RAII) as well as "immovable (limited) types", you should be able to make Ada equivalents now that it has a package manager like cargo, and there's already a version of Rc in one of the major libraries. However, you'd have to use the method discussed down below to control concurrent reads/writes in Ada, whereas in Rust the compiler will ensure you don't shoot yourself in the foot.

Pointers (access types) don't get used nearly as much use in Ada as they do in C or C++. Parameters to functions are immutable by default, unless they're indicated as being written to, an out parameter. There's some other rules about specific things being passed by reference --those with virtual function tables, and immovable (called "limited") objects.

In addition to locking access types to its own storage pool, there's another type of access which is access all, and can access any variable of its type, but only if it is marked as aliased. This helps limit down the cases of where accesses get used, but would be the main source of memory problems.

Threads

Rust's borrow checker only lets you lend out a single mutable reference to a variable at once. If you hand a mutable reference to a variable to a thread, the compiler prevents you from handing out another one.

Your other options are handing out mutexes instead which will let you interleave writes, or you can use message passing. (EDIT: It's been a while since I did this, help me ensure I'm still accurate).

For shared mutable state between multiple threads, Ada uses protected objects. These are sort of like a struct with functions which can only return values, and procedures which can read/write. This allows concurrent read access, while procedures prevent writes during reads. You can also define special function-like things called entries which are like a procedure but have a conditional guard called a "barrier". Barriers can be arbitrary conditions and get checked at the end of an entry or protected procedure to enable a waiting thread to proceed. Queueing gets handled by the language and there are tunable parameters via pragmas.


If Ada is already very safe, why Rust?

Read thread on https://users.rust-lang.org/t/if-ada-is-already-very-safe-why-rust/21911/5

EEC GDPR compliant