11
subtype plain_char is implementation-defined;
12
type ptrdiff_t is range implementation-defined;
13
type size_t is mod implementation-defined;
libc in Rust is the implementation of all those implementation-defined. For all platforms and all flavors. This pretty often means reordering as you frequently find subflavors that differ.
If everything goes well, you end up with rather clean interface definitions that don’t do much more than the Interfaces.C above mentions.
Also, libc is a crate, but not a “third-party” crate. It is a first-party crate.
Let’s not forget that there are platforms where these values are consciously not stable, e.g. BSDs, which leads to natural churn on our side (which is okay, but that also means our libc can never be fully “stable” in that sense). Ada implementations most likely have a similar part of their code-base.
Ada looks really nice, and I would like to give it a serious try some day. Alas, I think rust is more ergonomic for “normal” programming (read: non embedded, on a modern machine), and has more amenities I’m personally used to. That includes cargo (makes building a breeze) and a decent LSP server implementation (rust-analyzer). I know AdaCore has been working on tooling a lot but I’m not sure how far along it is.
Besides that, the syntax looks really a bit too verbose, but I think I can get over it if the feedback loop is tight enough — meaning again a good LSP server.
I think rust is more ergonomic for “normal” programming (read: non embedded, on a modern machine),
I’ve worked a fair bit with both languages and I’d say that, as far as pure language things are concerned, Ada is much easier to program in by virtue of not forcing you to think about lifetimes and borrowing. But this advantage quickly disappears when you try to use Spark’s equivalent concepts :).
That includes cargo (makes building a breeze)
The community effort Alire has reached v1.0 last week! The only thing really missing is the ton of crates that cargo has, but this is probably going to change now that there’s a central repository for libraries.
and a decent LSP server implementation (rust-analyzer)
There’s the ada_language_server but it’s true that it’s not as great as rust-analyzer (yet!). Syntax errors and jump-to-definition works really well though. There are also plans to add support for GCC’s -fdiagnostics-format=json to gnat, but it’s still a bit far from being ready.
Thanks for the pointers to Alire and the LSP server, good to know.
Ada is much easier to program in by virtue of not forcing you to think about lifetimes and borrowing.
See, that’s not clear to me. Is there an equivalent of Rc / Arc? How do you pass stuff by reference from the caller’s stack frame? I only ever read about Ada’s approach to dynamic allocation being “it’s manual and unsafe” so I’d love to read more about, well, ergonomic alternatives. The borrow checker in rust is pretty neat and not that hard in the common case (it can be horrible if you try to avoid all allocations and have to add complex lifetime annotations though).
AFAIK, there is no “language-level” Rc type. Instead, you build your own with controlled types (you can read more about this here).
I only ever read about Ada’s approach to dynamic allocation being “it’s manual and unsafe” so I’d love to read more about, well, ergonomic alternatives.
Ada has a thing named “secondary stack” that allows you to return variable-length arrays/types that look like they’re on the stack, and it is often enough to help you avoid “traditional” dynamic allocations.
However it’s true that when you really need to allocate something on the heap, you’ll have to do it in a way that is less safe than Rust (unless you prove your program with Spark - then you get a similar level of safety but also a similar level of complexity :) ).
The borrow checker in rust is pretty neat and not that hard in the common case
I agree that it’s pretty neat (wonderful even!) but I disagree that it’s “not that hard”. It disallows a ton of useful patterns - you can usually re-arrange your program in a way that pleases it and you eventually get used to it, but it’s still a very annoying restriction.
How would you compare the Ravenscar or Jorvik profiles of Ada + Spark against Rust’s Send and Sync marker traits? I’m often frustrated by how difficult it is to bolt down various undesirable properties in Rust, and I keep hearing nice things about Spark in terms of mixing proofs with programs.
Nice article! I’ve always wanted to sit down and play around with Ada, but never seem to find the time. This reminded me to try and make some time this year for it.
The representation clauses feature in Ada is something I’d love to have in other languages, especially Rust. In Rust you can control quite a bit about layout, but I wouldn’t say it’s particularly easy or straightfoward to do things like shown in the article. I feel the same way about this feature as I do about bit syntax in Erlang - it’s such a simple and convenient way to express the low-level representation of data that it is incredible to me that it isn’t more commonly seen in other languages. I suppose that, as with most things, the devil is in the details; but it does make me wonder just how difficult it would be to bring that syntax to a language like Rust or Go.
I also love that Ada has such a convenient way to define new scalar/primitive types while providing control over their range, and whether or not they are convertible from other scalar/primtive types. Rust comes close on this, with newtypes, or by defining new single-field struct types annotated with #[repr(transparent)] and then implementing all of the traits for things like arithmetic operators and such. Neither of those are quite as expressive and convenient as what Ada has in this regard.
This article doesn’t seem to even touch on one of Ada’s most interesting features (and the reason they taught us this language in my college): Ada has built-in and dead simple concurrency.
Ada is indeed a very interesting programming language and that article shows it well!
I want to expand on one thing, though. There’s the question in the post why implementing the
libc
crate is such an effort. It’s pretty simple. Let’s look at the interfaces definition from ADA: https://www.adaic.org/resources/add_content/standards/05rm/html/RM-B-3.htmllibc
in Rust is the implementation of all thoseimplementation-defined
. For all platforms and all flavors. This pretty often means reordering as you frequently find subflavors that differ.If everything goes well, you end up with rather clean interface definitions that don’t do much more than the Interfaces.C above mentions.
Here’s for example the definition for the Switch. https://github.com/rust-lang/libc/blob/0293c4492901e95fd03705f16086c6b74cba8b2a/src/switch.rs
Also,
libc
is a crate, but not a “third-party” crate. It is a first-party crate.Let’s not forget that there are platforms where these values are consciously not stable, e.g. BSDs, which leads to natural churn on our side (which is okay, but that also means our
libc
can never be fully “stable” in that sense). Ada implementations most likely have a similar part of their code-base.Ada looks really nice, and I would like to give it a serious try some day. Alas, I think rust is more ergonomic for “normal” programming (read: non embedded, on a modern machine), and has more amenities I’m personally used to. That includes cargo (makes building a breeze) and a decent LSP server implementation (rust-analyzer). I know AdaCore has been working on tooling a lot but I’m not sure how far along it is.
Besides that, the syntax looks really a bit too verbose, but I think I can get over it if the feedback loop is tight enough — meaning again a good LSP server.
I’ve worked a fair bit with both languages and I’d say that, as far as pure language things are concerned, Ada is much easier to program in by virtue of not forcing you to think about lifetimes and borrowing. But this advantage quickly disappears when you try to use Spark’s equivalent concepts :).
The community effort Alire has reached v1.0 last week! The only thing really missing is the ton of crates that cargo has, but this is probably going to change now that there’s a central repository for libraries.
There’s the ada_language_server but it’s true that it’s not as great as rust-analyzer (yet!). Syntax errors and jump-to-definition works really well though. There are also plans to add support for GCC’s -fdiagnostics-format=json to gnat, but it’s still a bit far from being ready.
Thanks for the pointers to Alire and the LSP server, good to know.
See, that’s not clear to me. Is there an equivalent of Rc / Arc? How do you pass stuff by reference from the caller’s stack frame? I only ever read about Ada’s approach to dynamic allocation being “it’s manual and unsafe” so I’d love to read more about, well, ergonomic alternatives. The borrow checker in rust is pretty neat and not that hard in the common case (it can be horrible if you try to avoid all allocations and have to add complex lifetime annotations though).
AFAIK, there is no “language-level” Rc type. Instead, you build your own with controlled types (you can read more about this here).
Ada has a thing named “secondary stack” that allows you to return variable-length arrays/types that look like they’re on the stack, and it is often enough to help you avoid “traditional” dynamic allocations.
However it’s true that when you really need to allocate something on the heap, you’ll have to do it in a way that is less safe than Rust (unless you prove your program with Spark - then you get a similar level of safety but also a similar level of complexity :) ).
I agree that it’s pretty neat (wonderful even!) but I disagree that it’s “not that hard”. It disallows a ton of useful patterns - you can usually re-arrange your program in a way that pleases it and you eventually get used to it, but it’s still a very annoying restriction.
How would you compare the Ravenscar or Jorvik profiles of Ada + Spark against Rust’s Send and Sync marker traits? I’m often frustrated by how difficult it is to bolt down various undesirable properties in Rust, and I keep hearing nice things about Spark in terms of mixing proofs with programs.
I’ve watched a few videos about Spark but I don’t know enough to answer authoritatively to this question, sorry.
I think Spark’s ability to prove that pre and post conditions hold is really good for this.
Nice article! I’ve always wanted to sit down and play around with Ada, but never seem to find the time. This reminded me to try and make some time this year for it.
The representation clauses feature in Ada is something I’d love to have in other languages, especially Rust. In Rust you can control quite a bit about layout, but I wouldn’t say it’s particularly easy or straightfoward to do things like shown in the article. I feel the same way about this feature as I do about bit syntax in Erlang - it’s such a simple and convenient way to express the low-level representation of data that it is incredible to me that it isn’t more commonly seen in other languages. I suppose that, as with most things, the devil is in the details; but it does make me wonder just how difficult it would be to bring that syntax to a language like Rust or Go.
I also love that Ada has such a convenient way to define new scalar/primitive types while providing control over their range, and whether or not they are convertible from other scalar/primtive types. Rust comes close on this, with newtypes, or by defining new single-field struct types annotated with
#[repr(transparent)]
and then implementing all of the traits for things like arithmetic operators and such. Neither of those are quite as expressive and convenient as what Ada has in this regard.This article doesn’t seem to even touch on one of Ada’s most interesting features (and the reason they taught us this language in my college): Ada has built-in and dead simple concurrency.