[HN Gopher] A comparison of Ada and Rust, using solutions to the...
       ___________________________________________________________________
        
       A comparison of Ada and Rust, using solutions to the Advent of Code
        
       Author : andsoitis
       Score  : 172 points
       Date   : 2025-10-04 15:10 UTC (7 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | yoyohello13 wrote:
       | Interesting that Ada has an open source compiler. For whatever
       | reason when I looked at it years ago I thought it was proprietary
       | compilers only so I never really looked at it again. Maybe I'll
       | look again now.
        
         | Jtsummers wrote:
         | GNAT has been around for 30 years. There were some limitations
         | with (one version of?) it due to it being released without the
         | GPL runtime exception, which meant linking against its runtime
         | technically required your programs to also be released under
         | GPL. That hasn't been an issue for a very long time either,
         | though.
        
         | sehugg wrote:
         | GNAT has been around since the 90s, based on GCC. My university
         | did some work on the compiler and used it for some undergrad
         | courses like real-time programming. IIRC there was an attempt
         | to use Ada for intro to programming courses, but I think they
         | chose Pascal and then eventually C++.
        
       | jonathanstrange wrote:
       | I'd love to use Ada as my primary static language if it had
       | broader support. It's in my opinion the best compiled language
       | with strong static typing. Although it has gained traction with
       | Alire, it unfortunately doesn't have enough 3rd party support for
       | my needs yet.
        
         | andsoitis wrote:
         | What 3rd party things would you like to see?
        
           | jonathanstrange wrote:
           | It's just everything and the kitchen sink, I'm afraid, from a
           | NATS server and client library over cross-platform GUIs
           | including mobile, common file format reading and writing like
           | Excel, Word, and PDF to post-quantum cryptography. I'm
           | unfortunately in the business of Lego-brick software
           | development using well-maintained 3rd-party libraries
           | whenever possible. It's the same with CommonLisp, I love it
           | but in the end I'd have to write too many things on my own to
           | be productive in it.
           | 
           | I envy people who can write foundational, self-contained
           | software. It's so elegant.
        
             | etrez wrote:
             | You have Alire crates for generating Excel and PDF
             | streams/files. Of course you want everything else ;-).
        
       | PaulKeeble wrote:
       | Ada has some really good ideas which its a shame never took off
       | or got used outside of the safety critical community that mostly
       | used it. The ability to make number types that were limited in
       | their range is really useful for certain classes of bugs. Spark
       | Ada was a relatively easy substandard to learn and apply to start
       | to develop software that was SIL 4 compliant.
       | 
       | I can't help but feel that we just went through a huge period of
       | growth at all costs and now there is a desire to return, after
       | 30-years of anything goes, to trying to make software that is
       | safer again. Would be nice to start to build languages based on
       | all the safety learnings over the decades to build some better
       | languages, the good ideas keep getting lost in obscure languages
       | and forgotten about.
        
         | Veliladon wrote:
         | > The ability to make number types that were limited in their
         | range is really useful for certain classes of bugs.
         | 
         | Yes! I would kill to get Ada's number range feature in Rust!
        
           | pjmlp wrote:
           | That feature is actually from Pascal, and Modula-2, before
           | making its way into Ada.
           | 
           | For some strange reason people always relate to Ada for it.
        
             | Veliladon wrote:
             | For me it's because I learned Ada in college.
             | 
             | 18 year old me couldn't appreciate how beautiful a language
             | it is but in my 40s I finally do.
        
             | jdrek1 wrote:
             | I would guess that Ada is simply more known. Keep in mind
             | that tech exploded in the past ~3.5 decades whereas those
             | languages are much older and lost the popularity contest.
             | If you ask most people about older languages, the replies
             | other than the obvious C and (kind of wrong but well) C++
             | are getting thin really quickly. COBOL, Ada, Fortran, and
             | Lisp are probably what people are aware of the most, but
             | other than that?
        
               | wpollock wrote:
               | You've forgotten about BASIC, SNOBOL, APL, Forth, and
               | PL/1. There were many interesting programming languages
               | back then. Good times!
        
               | aworks wrote:
               | The first five languages I learned back in the 70s:
               | FORTRAN, Pascal, PL/I, SNOBOL, APL. Then I was an Ada and
               | Icon programmer in the 80s. In the 90s, it was C/C++ and
               | I just never had the enthusiasm for it.
        
               | prerok wrote:
               | I found Pascal more readable as a budding programmer.
               | Later on, C's ability to just get out of the way to
               | program what I wanted trumped the Pascal's verbosity and
               | opinionatedness.
               | 
               | I admit that the terseness of the syntax of C can be off-
               | putting. Still, it's just syntax, I am sorry you were
               | disuaded by it.
        
               | fuzztester wrote:
               | True.
               | 
               | I dabbled in some of them during some periods when I took
               | a break from work. And also some, during work, in my free
               | time at home.
               | 
               | Pike, ElastiC (not a typo), Icon, Rebol (and later Red),
               | Forth, Lisp, and a few others that I don't remember now.
               | 
               | Not all of those are from the same period, either.
               | 
               | Heck, I can even include Python and Ruby in the list,
               | because I started using them (at different times, with
               | Python being first) much before they became popular.
        
             | sehugg wrote:
             | Turbo Pascal could check ranges on assignment with the
             | {$R+} directive, and Delphi could check arithmetic overflow
             | with {$Q+}. Of course, nobody wanted to waste the cycles to
             | turn those on :)
        
               | pjmlp wrote:
               | Most Pascal compilers could do that actually.
               | 
               | Yeah not wanting to waste cycles is how we ended up with
               | the current system languages, while Electron gets used
               | all over the place.
        
             | prerok wrote:
             | I would argue that was one of the reasons why those
             | languages lost.
             | 
             | I distinctly remember arguments for functions working on
             | array of 10. Oh, you want array of 12? Copy-paste the
             | function to make it array of 12. What a load of BS.
             | 
             | It took Pascal years to drop that constraint, but by then C
             | had already won.
             | 
             | I never ever wanted the compiler or runtime to check a
             | subrange of ints. Ever. Overflow as program crash would be
             | better, which I do find useful, but arbitrary ranges chosen
             | by programmer? No thanks. To make matters worse, those are
             | checked even by intermediate results.
             | 
             | I realize this is opinioned only on my experience, so I
             | would appreciate a counter example where it is a benefit
             | (and yes, I worked on production code written in Pascal,
             | French variant even, and migrating it to C was hilariously
             | more readable and maintainable).
        
               | pjmlp wrote:
               | Thankfully instead of overflow, C gets you the freedom of
               | UB based optimizations.
        
               | prerok wrote:
               | Funny :)
               | 
               | It still results in overflow and while you are right that
               | it's UB by the standard, it's still pretty certain what
               | will happen on a particular platform with a particular
               | compiler :)
        
               | gmadsen wrote:
               | compile time user config checking?
        
               | prerok wrote:
               | Sorry? That's not possible...
        
           | nicce wrote:
           | There is RFC but I guess the work stopped.
        
             | vacuity wrote:
             | As a sibling comment[0] mentioned, pattern types are
             | actively being worked on.
             | 
             | [0] https://news.ycombinator.com/item?id=45474777
        
               | nicce wrote:
               | Oh. I thought it stalled since there was a long time
               | without activity.
        
           | weinzierl wrote:
           | It is worked on under the term _" pattern types"_ mainly by
           | Oli oli-obk Scherer I think, who has an Ada background.
           | 
           | Can't tell you what the current state is but this should give
           | you the keywords to find out.
           | 
           | Also, here is a talk Oli gave in the Ada track at FOSDEM this
           | year: https://hachyderm.io/@oli/113970047617836816
        
         | jandrewrogers wrote:
         | > The ability to make number types that were limited in their
         | range is really useful for certain classes of bugs.
         | 
         | This is a feature I use a lot in C++. It is not part of the
         | standard library but it is trivial to programmatically generate
         | range-restricted numeric types in modern C++. Some safety
         | checks can even be done at compile-time instead of runtime.
         | 
         | It should be a standard feature in programming languages.
        
         | nickdothutton wrote:
         | 30+ years ago I was programming in Ada, and I feel the same way
         | and have been repeatedly disappointed. Maybe this time around
         | things will be different.
        
         | plainOldText wrote:
         | Nim was inspired by Ada & Modula, and has subranges [1]:
         | type         Age = range[0..200]            let ageWorks =
         | 200.Age       let ageFails = 201.Age
         | 
         | Then at compile time:                 $ nim c main.nim
         | Error: 201 can't be converted to Age
         | 
         | [1] https://nim-lang.org/docs/tut1.html#advanced-types-
         | subranges
        
           | arzig wrote:
           | What happens when you add 200+1 in a situation where the
           | compiler cannot statically prove that this is 201?
        
             | plainOldText wrote:
             | Your example also gets evaluated at comptime. For more
             | complex cases I wouldn't be able to tell you, I'm not the
             | compiler :) For example, this get's checked:
             | let ageFails = (200 + 2).Age       Error: 202 can't be
             | converted to Age
             | 
             | If it cannot statically prove it at comptime, it will crash
             | at runtime during the type conversion operation, e.g.:
             | import std/strutils            stdout.write("What's your
             | age: ")       let age = stdin.readLine().parseInt().Age
             | 
             | Then, when you run it:                 $ nim r main.nim
             | What's your age: 999       Error: unhandled exception:
             | value out of range: 999 notin 0 .. 200 [RangeDefect]
        
               | prerok wrote:
               | Exactly this. Fails at runtime. Consider rather a
               | different example: say the programmer thought the age
               | were constrained to 110 years. Now, as soon as a person
               | is aged 111, the program crashes. Stupid mistake by a
               | programmer assumption turns into a program crash.
               | 
               | Why would you want this?
               | 
               | I mean, we've recently discussed on HN how most sorting
               | algorithms have a bug for using ints to index into arrays
               | when they should be using (at least) size_t. Yet, for
               | most cases, it's ok, because you only hit the limit
               | rarely. Why would you want to further constrain the
               | field, would it not just be the source of additional
               | bugs?
        
               | fainpul wrote:
               | > Stupid mistake by a programmer assumption turns into a
               | program crash.
               | 
               | I guess you can just catch the exception in Ada? In Rust
               | you might instead manually check the age validity and
               | return Err if it's out of range. Then you need to handle
               | the Err. It's the same thing in the end.
               | 
               | > Why would you want to further constrain the field
               | 
               | You would only do that if it's a hard requirement (this
               | is the problem with contrived examples, they make no
               | sense). And in that case you would also have to implement
               | some checks in Rust.
        
               | prerok wrote:
               | Exactly, but how do you catch the exception? One
               | exception catch to catch them all, or do you have to
               | distinguish the types?
               | 
               | And yes... error handle on the input and you'd be fine.
               | How would you write code that is cognizant enough to
               | catch outofrange for every +1 done on the field?
               | Seriously, the production code then devolves into copying
               | the value into something else, where operations don't
               | cause unexpected exceptions. Which is a workaround for a
               | silly restriction that should not reside in runtime
               | level.
        
               | prerok wrote:
               | Also, I would be very interested to learn the case for
               | hard requirement for a range.
               | 
               | In almost all the cases I have seen it eventually breaks
               | out of confinement. So, it has to be handled sensibly.
               | And, again, in my experience, if it's built into
               | constraints, it invarianly is not handled properly.
        
               | SiempreViernes wrote:
               | Consider the size of the time step in a numerical
               | integrator of some chemical reaction equation, if it gets
               | too big the prediction will be wrong and your chemical
               | plant could explode.
               | 
               | So too big times steps cannot be used, but constant sized
               | steps is wasteful. Seems good to know the integrator can
               | never quietly be wrong, even if you have to pay the price
               | that tge integrator could crash.
        
               | wilsonnb3 wrote:
               | Once the program is operating outside of the bounds of
               | the programmers assumption, it's in an undefined state
               | that may cause a crash to happen at a later point of time
               | in a totally different place.
               | 
               | Making the crash happen at the same time and space as the
               | error means you don't have to trace a later crash back to
               | the root cause.
               | 
               | This makes your system much easier to debug at the
               | expense of causing some crashes that other systems might
               | not have. A worthy trade off in the right context.
        
               | prerok wrote:
               | Out of bounds exception is ok to crash the program. User
               | input error is not ok to crash the program.
               | 
               | I could go into many more examples but I hope I am
               | understood. I think these hard-coded definition of ranges
               | at compile time are causes of far more issues than they
               | solve.
               | 
               | Let's take a completely different example: size of a
               | field in a database for a surname. How much is enough?
               | Turns out 128 varchars is not enough, so now they've set
               | it to 2048 (not a project I work(ed) on, but am familiar
               | with). Guess what? Not in our data set, but
               | theoretically, even that is not enough.
        
               | Jtsummers wrote:
               | > Out of bounds exception is ok to crash the program.
               | User input error is not ok to crash the program.
               | 
               | So you validate user input, we've known how to do that
               | for decades. This is a non-issue. You won't crash the
               | program if you require temperatures to be between 0 and
               | 1000 K and a user puts in 1001, you'll reject the user
               | input.
               | 
               | If that user input crashes your program, you're not a
               | very good programmer, or it's a very early prototype.
        
           | wucke13 wrote:
           | I know quite some people in the safety/aviation domain that
           | kind of dislike the subranges, as it inserts run-time checks
           | that are not easily traceable to source code, thus escaping
           | the trifecta of requirements/tests/source-code (which all
           | must be traceable/covered by each other).
           | 
           | Weirdly, when going through the higher assurance levels in
           | aviation, defensive programming becomes _more_ costly,
           | because it complicates the satisfaction of assurance
           | objectives. SQLite (whiches test suite reaches MC /DC
           | coverage which is the most rigorous coverage criterion asked
           | in aviation) has a nice paragraph on the friction between
           | MC/DC and defensive programming:
           | 
           | https://www.sqlite.org/testing.html#tension_between_fuzz_tes.
           | ..
        
             | nine_k wrote:
             | Ideally, a compiler can statically prove that values stay
             | within the range; it's no different than proving that
             | values of an enumeration type are valid. The only places
             | where a check is needed are conversions from other types,
             | which are explicit and traceable.
        
               | estebank wrote:
               | If you have                   let a: u8 is 0..100 = 1;
               | let b: u8 is 0..100 = 2;         let c = a + b;
               | 
               | The type of c could be u8 in 0..200. If you have holes in
               | the middle, same applies. Which means that if you want to
               | make c u8 between 0..100 you'd have to explicitly
               | clamp/convert/request that, which would have to be a
               | runtime check.
        
               | nine_k wrote:
               | But obviously the result of a + b is [0..200], so an
               | explicit cast, or an assertion, or a call to clamp() is
               | needed if we want to put it back into a [0..100].
               | 
               | Comptime constant expression evaluation, as in your
               | example, may suffice for the compiler to be able to prove
               | that the result lies in the bounds of the type.
        
               | Jtsummers wrote:
               | In your example we have enough information to know that
               | the addition is safe. In SPARK, if that were a function
               | with a and b as arguments, for instance, and you don't
               | know what's being passed in you make it a pre-condition.
               | Then it moves the burden of proof to the caller to ensure
               | that the call is safe.
        
         | fuzztester wrote:
         | >Ada has some really good ideas which its a shame never took
         | off or got used outside of the safety critical community that
         | mostly used it. The ability to make number types that were
         | limited in their range is really useful for certain classes of
         | bugs.
         | 
         | As pjmlp says in a sibling comment, Pascal had this feature,
         | from the beginning, IIRC, or from an early version - even
         | before the first Turbo Pascal version.
        
         | wolvesechoes wrote:
         | Ada, or at least GNAT, also supports compile-time dimensional
         | analysis (unit checking). I may be biased, because I mostly
         | work with engineering applications, but I still do not
         | understand why in other languages it is delegated to 3rd party
         | libraries.
         | 
         | https://docs.adacore.com/gnat_ugn-docs/html/gnat_ugn/gnat_ug...
        
         | wilsonnb3 wrote:
         | It doesn't really compete in the same space as Ada or Rust but
         | C# has range attributes that are similar, the only downside is
         | you have to manually call the validation function unless you
         | are using something like ASP.NET that does it automatically at
         | certain times.
        
         | c0balt wrote:
         | In my personal experience it's not just safety. Reliability of
         | produced is also a big part.
         | 
         | Ime, being able to express constraints in a type systems yields
         | itself to producing better quality code. A simple example from
         | my experience with rust and golang is mutex handling, rust just
         | won't let you leak a guard handle while golang happily let's
         | you run into a deadlock.
        
       | commandar wrote:
       | Tangentially related, one of the more interesting projects I've
       | seen in the 3D printing space recently is Prunt. It's a printer
       | control board and firmware, with the latter being developed in
       | Ada.
       | 
       | https://prunt3d.com/
       | 
       | https://github.com/Prunt3D/prunt
       | 
       | It's kind of an esoteric choice, but struck me as "ya know,
       | that's really not a bad fit in concept."
        
       | bigstrat2003 wrote:
       | I found it kind of odd that the author says Rust doesn't support
       | concurrent programming out of the box. He links to another
       | comment which points out you don't need Tokio for async (true
       | enough), but even that aside async isn't the only way to achieve
       | concurrency. Threads are built right into the language, _and_ are
       | easier to use than async. The only time they wouldn 't be a good
       | choice is if you anticipate needing to spawn so many threads that
       | it causes resource issues, which very few programs will.
        
         | quietbritishjim wrote:
         | (Honest question from non Rustacean.)
         | 
         | How does the cancellation story differ between threads and
         | async in Rust? Or vs async in other languages?
         | 
         | There's no inherent reason they should be different, but in my
         | experience (in C++, Python, C#) cancellation is much better in
         | async then simple threads and blocking calls. It's near
         | impossible to have organised socket shutdown in many languages
         | with blocking calls, assuming a standard read thread + write
         | thread per socket. Often the only reliable way to interrupt a
         | socket thread it's to close the socket, which may not be what
         | you want, and in principle can leave you vulnerable to file
         | handle reuse bugs.
         | 
         | Async cancellation is, depending on the language, somewhere
         | between hard but achievable (already an improvement) and
         | fabulous. With Trio [1] you even get the guarantee that non-
         | compared socket operations are either completed or have no
         | effect.
         | 
         | Did this work any better in Rust threads / blocking calls? My
         | uneducated understanding is that things are actually worse in
         | async than other languages because there's no way to catch and
         | handle cancellations (unlike e.g. Python which uses exceptions
         | for that).
         | 
         | I'm also guessing things are no better in Ada but very happy to
         | hear about that too.
        
           | IshKebab wrote:
           | > There's no inherent reason they should be different
           | 
           | There is... They're totally different things.
           | 
           | And yeah Rust thread cancellation is pretty much the same as
           | in any other language - awkward to impossible. That's a
           | fundamental feature of threads though; nothing to do with
           | Rust.
        
             | quietbritishjim wrote:
             | To be clear about what I meant: I was saying that, in
             | principle, it would be possible design a language or even
             | library where all interruptable operations (at least timers
             | and networking) can be cancelled from other threads. This
             | can be done using a cancellation token mechanism which
             | avoids even starting the operation of already cancelled
             | token, in a way that avoids races (as you might imagine
             | from a naive check of a token before starting the
             | operation) if another thread cancels this one just as the
             | operation is starting.
             | 
             | Now I've set (and possibly moved) the goalposts, I can
             | prove my point: C# already does this! You can use async
             | across multiple threads and cancellation happens with
             | cancellation tokens that are thread safe. Having a version
             | where interruptable calls are blocking rather than async
             | (in the language sense) would actually be easier to
             | implement (using the same async-capable APIs under the hood
             | e.g., IOCP on Windows).
        
               | IshKebab wrote:
               | Well sure, there's nothing to stop you writing a
               | "standard library" that exposes that interface. The
               | default one doesn't though. I expect there are platforms
               | that Rust supports that don't have interruptible timers
               | and networking (whereas C# initially only supported
               | Windows).
        
             | jvanderbot wrote:
             | There's no explicit cancel, but there's trivial one shot
             | cancellation messages that you can handle on the thread
             | side. It's perfectly fine, honestly, and how I've been
             | doing it forever.
        
               | IshKebab wrote:
               | I would call that clean shutdown more than cancellation.
               | You can't cancel a long computation, or
               | std::thread::sleep(). Though tbf that's sort of true of
               | async too.
        
           | bryanlarsen wrote:
           | Cancellation in rust async is almost too easy, all you need
           | to do is drop the future.
           | 
           | If you need cleanup, that still needs to be handled manually.
           | Hopefully the async Drop trait lands soon.
        
             | quietbritishjim wrote:
             | Yeah that's what I'm talking about ... Cancellation where
             | the cancelled object can't handle the cancellation, call
             | other async operations and even (very rarely) suppress it,
             | isn't "real" cancellation to me, having seen how this
             | essential it is.
        
             | jvanderbot wrote:
             | Ok I could be super wrong here, but I think that's not
             | true.
             | 
             | Dropping a future does not cancel a concurrently running
             | (tokio::spawn) task. It will also not magically stop an
             | asynchronous I/o call, it just won't block/switch from your
             | code anymore while that continues to execute. If you have
             | created a future but not hit .await or tokio::spawn or any
             | of the futures:: queue handlers, then it also won't cancel
             | it it just won't begin it.
             | 
             | Cancellation of a running task from outside that task
             | actually does require explicit cancelling calls IIRC.
             | 
             | Edit here try this https://cybernetist.com/2024/04/19/rust-
             | tokio-task-cancellat...
        
               | saghm wrote:
               | Spawn is kind of a special case where it's documented
               | that the future will be moved to the background and
               | polled without the caller needing to do anything with the
               | future it returns. The vast majority of futures are lazy
               | and will not do work unless explicitly polled, which
               | means the usual way of cancelling is to just stop polling
               | (e.g. by awaiting the future created when joining
               | something with a timeout; either the timeout happens
               | before the other future completes, or the other future
               | finishes and the timeout no longer gets polled). Dropping
               | the future isn't technically a requirement, but in
               | practice it's usually what will happen because there's no
               | reason to keep around a future you'll never poll again,
               | so most of the patterns that exist for constructing a
               | future that finishes when you don't need it anymore
               | rather than manually cancelling will implicitly drop any
               | future that won't get used again (like in the join
               | example above, where the call to `join` will take
               | ownership of both futures and not return either of them,
               | therefore dropping whichever one hasn't finished when
               | returning).
        
             | foresterre wrote:
             | Rain showed that not all may be as simple as it seems to do
             | it correctly.
             | 
             | In her presentation on async cancellation in Rust, she
             | spoke pretty extensively on cancel safety and correctness,
             | and I would recommend giving it a watch or read.
             | 
             | https://sunshowers.io/posts/cancelling-async-rust/
        
         | IshKebab wrote:
         | Or in cases where the platform doesn't support threads easily -
         | WASM and embedded (Embassy). Tbh I think that's a better
         | motivation for using async than the usual "but what if 100k
         | people visit my anime blog all at once?"
        
         | tialaramex wrote:
         | I wonder where the cut-off is where a work stealing scheduler
         | like Tokio's is noticeably faster than just making a bunch of
         | threads to do work, and then where the hard cut-off is that
         | making threads will cause serious problems rather than just
         | being slower because we don't steal.
         | 
         | It might be quite small, as I found for Maps (if we're putting
         | 5 things in the map then we can just do the very dumbest thing
         | which I call `VecMap` and that's fine, but if it's 25 things
         | the VecMap is a little worse than any actual hash table, and if
         | it's 100 things the VecMap is laughably terrible) but it might
         | be quite large, even say 10x number of cores might be just fine
         | without stealing.
        
           | galangalalgol wrote:
           | The general rule is that if you need to wait faster use
           | async, and if you need to process faster use threads.
        
             | asa400 wrote:
             | Another way of thinking about this is whether you want to
             | optimize your workload for throughput or latency. It's
             | almost never a binary choice, though.
        
             | jjfoooo4 wrote:
             | Can you say more about what you mean by wait faster? Is it
             | as in, enqueue many things faster?
        
             | vacuity wrote:
             | Threads as they are conventionally considered are
             | inadequate. Operating systems should offer something along
             | the lines of scheduler activations[0]: a low-level
             | mechanism that represents individual cores being
             | scheduled/allocated to programs. Async is responsive simply
             | because it conforms to the (asynchronous) nature of
             | hardware events. Similarly, threads are most performant if
             | leveraged according to the usage of hardware cores. A
             | program that spawns 100 threads on a system with 10
             | physical cores is just going to have threads interrupting
             | each other for no reason; each core can only do so much
             | work in a time frame, whether it's running 1 thread or 10.
             | The most performant/efficient abstraction is a state
             | machine[1] per core. However, for some loss of performance
             | and (arguable) ease of development, threads can be used on
             | top of scheduler activations[2]. Async on top of threads is
             | just the worst of both worlds. Think in terms of the
             | hardware resources and events (memory accesses too), and
             | the abstractions write themselves.
             | 
             | [0] https://en.wikipedia.org/wiki/Scheduler_activations,
             | https://dl.acm.org/doi/10.1145/121132.121151 | Akin to
             | thread-per-core
             | 
             | [1] Stackless coroutines and event-driven programming
             | 
             | [2] User-level virtual/green threads today, plus
             | responsiveness to blocking I/O events
        
         | WD-42 wrote:
         | You may be correct in theory though in practice the reason to
         | use Async over threads is often because the crate you want to
         | use is async. Reqwest is a good example, it cannot be used
         | without Tokio. Ureq exists and works fine. I've done a fairly
         | high level application project where I tried to avoid all async
         | and at some point it started to feel like swimming upstream.
        
       | imglorp wrote:
       | The author indicates some obvious differences, including the fact
       | that Ada has a formal spec and rust doesn't -- rustc seems to be
       | both in flux as well as the reference implementation. This might
       | matter if you're writing a new compiler or analyzer.
       | 
       | But the most obvious difference, and maybe most important to a
       | user, was left unstated: the adoption and ecosystem such as
       | tooling, libraries, and community.
       | 
       | Ada may have a storied success history in aerospace and life
       | safety, etc, and it might have an okay standard lib which is fine
       | for AOC problems and maybe embedded bit poking cases in which
       | case it makes sense to compare to Rust. But if you're going to
       | sit down for a real world project, ie distributed system or OS
       | component, interfacing with modern data formats, protocols, IDEs,
       | people, etc is going to influence your choice on day one.
        
         | vollbrecht wrote:
         | Rust has now a donated spec that was provided by Ferrocene.
         | This spec style was influenced by the Ada spec. It is available
         | publicly now on https://rust-lang.github.io/fls/ .
         | 
         | This is part of the effort of Ferrocene to provide a safety
         | certificate compiler. And they are already available now.
        
           | gkbrk wrote:
           | This is only meaningful if Rust compiler devs give any
           | guarantees about never breaking the spec and always being
           | able to compile code that adheres to this spec.
        
             | tialaramex wrote:
             | How is this different from the existing situation that Rust
             | remains compatible since Rust 1.0 over a decade ago?
        
             | the_duke wrote:
             | Why so?
             | 
             | Specs for other languages are also for a specific
             | version/snapshot.
             | 
             | It's also a specific version of a compiler that gets
             | certified, not a compiler in perpetuity, no matter what
             | language.
        
               | jlarocco wrote:
               | That's _not_ how it works for most language standards,
               | though. Most language standards are prescriptive, while
               | Rust is descriptive.
               | 
               | Usually the standard comes first, compiler vendors
               | implement it, and between releases of the spec the
               | language is fixed. Using Ada as an example, there was Ada
               | 95 and Ada 2003, but between 95 and 2003 there was only
               | Ada 95. There was no in-progress version, the compiler
               | vendors weren't making changes to the language, and an
               | Ada95 compiler today compiles the same language as an
               | Ada95 compiler 30 years ago.
               | 
               | Looking at the changelog for the Rust spec (https://rust-
               | lang.github.io/fls/changelog.html), it's just the
               | changelog of the language as each compiler verion is
               | released, and there doesn't seem to be any intention of
               | supporting previous versions. Would there be any point in
               | an alternative compiler implementing "1.77.0" of the Rust
               | spec?
               | 
               | And the alternative compiler implementation can't start
               | implementing a compiler for version n+1 of the spec until
               | that version of rustc is released because "the spec" is
               | just "whatever rustc does", making the spec kind of
               | pointless.
        
               | steveklabnik wrote:
               | > Usually the standard comes first, compiler vendors
               | implement it, and between releases of the spec the
               | language is fixed.
               | 
               | This is not how C or C++ were standardized, nor most
               | computer standards in the first place. Usually, vendors
               | implement something, and then they come together to agree
               | upon a standard second.
               | 
               | When updating standards, sometimes things are put in the
               | standard before any implementations, but that's generally
               | considered an antipattern for larger designs. You want
               | real-world evaluation of the usefulness of something
               | before it's been standardized.
        
               | beeflet wrote:
               | Because otherwise the spec is just words on a paper, and
               | the standard is just "whatever the compiler does is what
               | it supposed to do". The spec codifies the intentions of
               | the creators separately from the implementation.
        
             | pornel wrote:
             | By that criteria there's no meaningful C++ compiler/spec.
        
         | wucke13 wrote:
         | Neither the Rust nor the Ada spec is formal, in the sense of
         | consumable by a theorem prover. AFAIK for Ada Spark, there is
         | of course assumptions on the language semantics built-in to
         | Spark, but: these are nowhere coherently written down in a
         | truly formal (as in machine-readable) spec.
        
           | Sharlin wrote:
           | What even _is_ the most complex programming language with a
           | fully machine-checkable spec? Are there actually practically
           | useful ones? I know of none.
        
             | nine_k wrote:
             | One candidate is ATS [1].
             | 
             | Another, https://cakeml.org/
             | 
             | [1]:
             | https://en.wikipedia.org/wiki/ATS_(programming_language)
        
           | shakna wrote:
           | There was also Larch/Ada [0], which was a formally proved
           | subset of Ada, developed for NASA [1].
           | 
           | [0] https://apps.dtic.mil/sti/tr/pdf/ADA249418.pdf
           | 
           | [1] https://ntrs.nasa.gov/citations/19960000030
        
         | SiempreViernes wrote:
         | I'm sure the programmers of the flight control software safely
         | transporting 1 billion people per year see your "real world
         | project" and reply with something like "yes, if you are writing
         | software where the outputs don't matter very much, our
         | processes are excessive" :p
        
       | Zak wrote:
       | I found the inclusion of arrays indexed on arbitrary types in the
       | feature table as a benefit of Ada surprising. That sounds like a
       | dictionary type, which is in the standard library of nearly every
       | popular Language. Rust includes two.
        
         | pixelesque wrote:
         | It's not a dictionary, that's a totally different data
         | structure.
         | 
         | In ADA you can subtype the index type into an array, i.e.
         | constraining the size of the allowed values.
        
           | Jtsummers wrote:
           | The Americans with Disabilities Act doesn't cover subtyping
           | the index type into an array. Ada, the language, does though.
           | 
           | EDIT: Seems I'm getting downvoted, do people not know that
           | ADA is not the name of the programming language? It's Ada, as
           | in Ada Lovelace, whose name was also not generally SHOUTED as
           | ADA.
        
             | tialaramex wrote:
             | There does seem to be a strain of weird language fanatics
             | who insist all programming language names must be shouted,
             | so they'll write RUST and ADA, and presumably JAVA and
             | PYTHON, maybe it's an education thing, maybe they're stuck
             | in an environment with uppercase only like a 1960s FORTRAN
             | programmer ?
        
               | DontchaKnowit wrote:
               | Maybe who cares?
        
               | bitwize wrote:
               | It's funny because Fortran's official name now is
               | Fortran, not FORTRAN.
        
               | jghn wrote:
               | I have found a strong correlation between people who say
               | JAVA and country of origin. And thus have assumed it's an
               | education thing.
        
         | tialaramex wrote:
         | I think they're focused very much specifically on the built-in
         | array type. Presumably Ada is allowed to say eggs is an array
         | and the index has type BirdSpecies so eggs[Robin] and
         | eggs[Seagull] work but eggs[5] is nonsense.
         | 
         | Rust is OK with you having a type which implements
         | Index<BirdSpecies> and if eggs is an instance of that type it's
         | OK to ask for eggs[Robin] while eggs[5] won't compile, but Rust
         | won't give you an "array" with this property, you'd have to
         | make your own.
         | 
         | My guess is that this makes more sense in a language where user
         | defined types are allowed to be a subset of say a basic integer
         | type, which I know Ada has and Rust as yet does not. If you can
         | do that, then array[MyCustomType] is very useful.
         | 
         | I call out specifically User Defined types because, Rust's
         | NonZeroI16 - the 16-bit integers except zero - is compiler-only
         | internal magic, if you want a MultipleOfSixU32 or even
         | U8ButNotThreeForSomeReason that's not "allowed" and so you'd
         | need nightly Rust and an explicit "I don't care that this isn't
         | supported" compiler-only feature flag in your source. I want to
         | change this so that _anybody_ can make the
         | IntegersFiveThroughTwelveU8 or whatever, and there is non zero
         | interest in that happening, but I 'd have said the exact same
         | thing two years ago so...
        
           | sureglymop wrote:
           | I really don't understand this so I hope you won't mind
           | explaining it. If I would have the type
           | U8ButNotThreeForSomeReason wouldn't that need a check at
           | runtime to make sure you are not assigning 3 to it?
        
             | kbolino wrote:
             | Option<U8ButNotThreeForSomeReason> would have a size of 2
             | bytes (1 for the discriminant, 1 for the value) whereas
             | Option<NonZeroU8> has a size of only 1 byte, thanks to some
             | special sauce in the compiler that you can't use for your
             | own types. This is the only "magic" around NonZero<T> that
             | I know of, though.
        
               | tialaramex wrote:
               | You can make an enum, with all 255 values spelled out,
               | and then write lots of boilerplate, whereupon
               | Option<U8ButNotThreeForSomeReason> is also a single byte
               | in stable Rust today, no problem.
               | 
               | That's kind of silly for 255 values, and while I suspect
               | it would _work_ clearly not a reasonable design for
               | 16-bits let alone 32-bits where I suspect the compiler
               | will reject this wholesale.
               | 
               | Another trick you can do, which will also work just fine
               | for bigger types is called the "XOR trick". You _store_ a
               | NonZero <T> but all your adaptor code XORs with your
               | single not-allowed value, in this case 3 and this is
               | fairly cheap on a modern CPU because it's an ALU
               | operation, no memory fetches except that XOR instruction,
               | so often there's no change to bulk instruction
               | throughput. This works because only 3 XOR 3 == 0, other
               | values will all have bits jiggled but remain valid.
               | 
               | Because your type's storage is the same size, you get all
               | the same optimisations and so once again
               | Option<U8ButNotThreeForSomeReason> is a single byte.
        
             | tialaramex wrote:
             | At _runtime_ it depends. If we 're using arbitrary outside
             | integers which might be three, we're obliged to check yes,
             | nothing is for free. But perhaps we're mostly or entirely
             | working with numbers we know a priori are never three.
             | 
             | NonZero<T> has a "constructor" named new() which returns
             | Option<NonZero<T>> so that None means nope this value isn't
             | allowed because it's zero. But unwrapping or expecting an
             | Option is constant, so NonZeroI8::new(9).expect("Nine is
             | not zero") will compile and produce a constant that the
             | type system knows isn't zero.
             | 
             | Three in particular does seem like a weird choice, I want
             | Balanced<signed integer> types such as BalancedI8 which is
             | the 8-bit integers including zero, -100 and +100 but
             | crucially _not_ including -128 which is annoying but often
             | not needed. A more general system is envisioned in
             | "Pattern Types". How much more general? Well, I think
             | proponents who want lots of generality need to help deliver
             | that.
        
         | Jtsummers wrote:
         | Ada also has hash maps and sets.
         | 
         | http://www.ada-auth.org/standards/22rm/html/RM-TOC.html - See
         | section A.18 on Containers.
         | 
         | The feature of being able to use a discrete range as an array
         | index is very helpful when you have a dense map (most keys will
         | be used) or you also want to be able to iterate over a
         | sequential block of memory (better performance than a
         | dictionary will generally give you, since they don't usually
         | play well with caches).
        
           | Zak wrote:
           | Thanks for the clarification. I can imagine that being a
           | useful optimization on occasion.
        
       | Surac wrote:
       | Very nice text. As i am very sceptical to Rust i apreciate the
       | comarison to a language i know better. I was not aware that there
       | is no formal spec for rust. Isn't that a problem if rustc makes
       | changes?
        
         | usamoi wrote:
         | It depends on who you are.
         | 
         | For implementers of third-party compilers, researchers of the
         | Rust programming language, and programmers who write unsafe
         | code, this is indeed a problem. It's bad.
         | 
         | For the designers of Rust, "no formal specification" allows
         | them to make changes as long as it is not breaking. It's good.
        
           | Surac wrote:
           | Medical or Miltary often require the software stack/tooling
           | to be certified following certain rules. I know most of this
           | certifications are bogus, but how is that handled with Rust?
        
             | detaro wrote:
             | Ferrocene provides a certified compiler (based on a spec
             | they've written documenting how it behaves) which is usable
             | for many uses cases, but it obviously depends what exactly
             | your specific domain needs.
        
         | steveklabnik wrote:
         | A few things:
         | 
         | 1. There is a spec now, Ferrocene donated theirs, and the
         | project is currently integrating it
         | 
         | 2. The team takes backwards compatibility seriously, and uses
         | tests to help ensure the lack of breakage. This includes tests
         | like "compile the code being used by Linux" and "compile most
         | open source Rust projects" to show there's no regressions.
        
           | SabrinaJewson wrote:
           | Ferrocene is a specification but it's not a formal
           | specification.
           | [Minirust](https://github.com/minirust/minirust) is the
           | closest thing we have to a formal spec but it's very much a
           | work-in-progress.
        
             | steveklabnik wrote:
             | It's a good enough spec to let rustc work with safety
             | critical software, so while something like minirust is
             | great, it's not necessary, just something that's nice to
             | have.
        
             | Ar-Curunir wrote:
             | Isn't the Ada spec also not a formal spec?
        
       | jll29 wrote:
       | This write-up shows that while Ada may have some cultural and
       | type-related disadvantages compared to Rust, Ada seems to
       | generally win the readability contest.
       | 
       | What is missing from the comparison is compiler speed - Ada was
       | once seen as a complex language, but that may not be the case if
       | compared against Rust.
       | 
       | In any case, thanks for the post, it made me want to try using
       | Ada for a real project.
        
       | tialaramex wrote:
       | In Case Study 2, near the end it says "if the client may need to
       | know SIDE_LENGTH, then you can add a function to return the
       | value"
       | 
       | Which yeah, you can do that but it's a constant so you can also
       | more literally write (in the implementation just like that
       | function):                       pub const SIDE_LENGTH: usize =
       | ROW_LENGTH;
        
       | garethrowlands wrote:
       | On strings in Ada vs Rust. Ada's design predates Unicode (early
       | 1980s vs 1991), so Ada String is basically char array whereas
       | Rust string is a Unicode text type. This explains why you can
       | index into Ada Strings, which are arrays of bytes, but not into
       | Rust strings, which are UTF8 encoded buffers that should be
       | treated as text. Likely the Rust implementation could have used a
       | byte array here.
        
         | debugnik wrote:
         | > Ada String is basically char array
         | 
         | Worse, the built-in Unicode strings are arrays of Unicode
         | scalars, effectively UTF-32 in the general case. There's no
         | proper way to write UTF-8 string literals AFAIK, you need to
         | convert them from arrays of 8, 16 or 32 bit characters
         | depending on the literal.
        
           | gjadi wrote:
           | How is the internal representation an issue? Java string are
           | utf16 internally and it's doesn't matter how you write your
           | code nor what's the targeted format.
        
       | ummonk wrote:
       | I'd disagree that both languages encourage stack-centric
       | programming idioms. Ada encourages static allocation instead.
        
       ___________________________________________________________________
       (page generated 2025-10-04 23:00 UTC)