Can Zig be the new Ada?

I have often heard Zig to be the ‘successor to C’, or even ‘a better C’. While that might or might not be the case, Zig might actually (also) be the ‘successor’ to another system programming language, Ada.

Ada is a programming language from the 80s for embedded, real-time safety critical systems. It was developed partially by the US military. While new projects rarely use it today, it is still widely used in existing systems in the defence industry and in aviation.
Most people I’ve talked to that have heard of Ada, usually think of SPARK, which is a formal verification system (a language extension in a sense) for Ada. SPARK is rarely used, as it hasn’t been available that long. Normal Ada is still a considerably safe language.
Which makes the comparison to Zig interesting, because feature-wise Ada was ahead of its time.
Ignoring petty syntax comparisons, the languages are quite comparable to an extent:

  • Ada enforces a large number of checks at compile time, to catch common mistakes. Extremely strict typing prevents a lot of possible bugs. An example are very range violations on arrays. There is also a (payed) static analysis tool, which catches a lot of potential bugs, like use after free. The Zig compiler can currently do most of this analysis (for free, as I might add).
  • While pointer usage is limited, untyped pointers never exist. Usually one can rely on the compiler to make smart decisions. Most stuff is just passed as slices under the hood.
  • Generics are comparable. Ada has package (namespace) wide compile-time generics. A lot of typ strictness is enforced, but if you think of packages as structs (as they are in Zig), it is functionally the same thing.
  • The natural way to write Ada code is to write data oriented code. While OOP exists to some degree, its ergonomics are terrible. Therefor, it’s usually only good for Object dot Function syntax and a form of scope based resource handling (constructor, destructor, but weird). The heavy lifting is usually done with variant records (packed unions).
  • It provides a container library in the std. This isn’t special but it prevents most of the use cases for manual memory management.
  • A lot of heavy lifting is done through enums. Interestingly, the enum data structures in the zig std all have equivalents in Ada, but not as containers, but as array like types.
  • It supports a form of region based memory management. It’s not as powerful as zig allocators, but you’re still able to write slab and arena like ‘pools’.

While the languages do not look the same, it feels surprisingly simular to write Ada and Zig.
However, why post this here? If I haven’t entertained you so far, I think Zig can also take some lessons from Ada. There are also a few features from Ada, I’d like to see in Zig.

  • It is generally a very good idea, to have a high quality large std, in favor of a package manager. Rust programs have reached npm levels of dependencies and I blame the comfort of finding and adding packages. Since Ada never was a widely adopted language, outside of specific industries, there are few to no open source libraries one could use. This forced people to write their own libraries. As a result, all Ada projects I’ve seen had few to no dependencies. A ‘boost’ like library doesn’t exist. I hope that Zig never adds a package manager or a public zig repository. After Zig 1.0, I would really like the std to grow, as e.g. a lot of useful but less commonly used data structures are currently missing.
  • Do not fall into the trap of overstrict typing. I’ve seen a proposal for a very complicated typedef, which looked very simular to adas types and subtypes (which have very complicated coercion rules). It is not a good idea; People lack the discipline to use the correctly. Either it is distinct, or it isn’t, and these use cases are covered by enums. Range types however sound like a great ideas; They make handles distinct from ‘real integers’ in cases where you can’t use slices.
  • I would really like real time capabilities for Zig, and I think it’s headed the right way. Ada has the Ravenscar profiler, hard real time concurrent systems, which makes code safe and predictable. To my knowledge, it is the only compiled language to have such a standard as part of its language and compiler specification. In Zig, I can easily imagine a real time IO interface and a real time allocator. If you think about it, the Zig compiler could also have simular capabilities to Ravenscar. This would be about proving, that code is deterministic. As async/allocations are ‘just code’, you could have purely semantics based analysis. A lot of hard real time coding patterns are already common or baked into Zig if you think about it. E.g. it is common to give upper limits to while loops. Zig also prevents some anti real time patterns all together; Dispatch is static by default, or implemented through tagged unions. (Is Unbounded recursion detected by the zig compiler btw?)

I hope this was decently interesting to read.

I’ve been writing some real-time software in zig for industrial controls (EtherCAT). jeffective/gatorcat: EtherCAT MainDevice for Zig - Codeberg.org

I also see huge potential for zig in real-time and safety critical systems.

I’m interested to hear more about Ravenscar. What features does it have and did it require compiler integration? What guarantees does it provide? I have never heard of it before.

I also think we need more contract programming capabilities. Which something like result location: ability to refer to the return result location before the `return` statement · Issue #2765 · ziglang/zig · GitHub would enable (to allow post-conditons).

One place were zig can potential advance farther than almost every other language is eliminating stack overflow with compile time know stack usage: Builtin function to tell you the maximum stack size of a given function · Issue #157 · ziglang/zig · GitHub

Which is also a prerequisite to stackless coroutines, which would be insane to have zero-allocation or bounded allocations for async on embedded.

Regarding your question about Ravenscar: It is part of the language specification and a so called Profile. A Profile is a set of compiler constrains, a compiler may or may not implement. E.g. there is also an annex foe garbage collection, but no compiler ever implemented it. The GNAT compiled aulports the Ravenscar profile.

It mainly constrains the tasking model of Ada. Certain std features are also forbidden. Basicly in Ada you have Tasks (comparable to java threads) and protected objects (work like monitors). It gurantees that preemptive scheduling is always possible. As you certainly know, this is a precondition for hard real time applications. Only one task can ever wait on a protected object; This minimizes blocking time as far as I understand. There is also a locking policy which prevents deadlocks.

If you are interested, you can read up on it here.