Essay · Software & Ideas

The Quiet
Colossus

On Ada, the language that the Department of Defense built, the industry ignored, and every modern language quietly became

There is a language that made generics a first-class, standardised feature of a widely deployed systems language, formalised the package, built concurrency into the specification rather than the library, mandated the separation of interface from implementation, and introduced range-constrained types, discriminated unions, and a model of task communication that Go would arrive at, independently and by a different route, thirty years later. Successive revisions added protected objects, compile-time null exclusion, and language-level contracts. It is a language that Rust spent a decade converging toward from one direction while Python converged toward it from another, and that C# has been approximating, feature by feature, for the better part of two decades. It is a language that the industry has consistently described as verbose, arcane, and irrelevant. It is also, with a directness that embarrasses the usual story of software progress, the language that anticipated — with unusual precision — the safety features every modern language is now trying to acquire.

Ada is not famous. It is not the subject of enthusiastic conference talks or breathless blog posts. It does not have a charismatic founder who gives keynotes about the philosophy of programming, and it does not have a community that writes frameworks or publishes packages with clever names. What it has is a formal standard that has been revised four times since 1983; a presence in the software of many major commercial aircraft and avionics systems; a set of design decisions made under government contract in the late 1970s that the rest of the industry has spent forty years independently rediscovering; and a reputation, among the programmers who know it at all, as the language that says no — the language whose compiler enforces legality, visibility, typing, and a degree of safety checking that most languages leave to convention or tooling, that makes the programmer name what they mean, that treats ambiguity as an error rather than a feature. These qualities were, for a long time, considered its weaknesses. They are, on examination, the precise qualities that every language currently described as modern is attempting to acquire.

· · ·

To understand why Ada exists requires understanding the particular crisis that produced it — a crisis not of computer science but of procurement, one that the United States Department of Defense encountered in the early 1970s when it attempted to survey the software that ran its weapons systems, logistics infrastructure, and command-and-control apparatus. What the survey found was not a software monoculture. It was the opposite: a proliferation of over four hundred and fifty distinct programming languages and dialects in active use across DoD systems, each one associated with a particular contractor or a particular era of development, none interoperable with any other, most unmaintainable by anyone except the original authors, many of those authors no longer available.1 The software that guided missiles could not be maintained by the people who maintained the software that navigated ships. The software that scheduled logistics could not share code with the software that processed communications. The languages had accumulated the way technical debt accumulates: invisibly, incrementally, each individual decision locally reasonable, the aggregate catastrophic.

The DoD's response was, for a government body, unusually sophisticated. Rather than simply mandating an existing language — COBOL, Fortran, and PL/1 were all considered and rejected — it undertook a requirements process that lasted five years and produced a series of documents of increasing precision: Strawman, Woodenman, Tinman, Ironman, and finally Steelman, each one refining and tightening the specification of what a DoD programming language must be. The Steelman document, issued in 1978, is a remarkable piece of engineering requirements literature: it does not specify a language, but describes the properties a language must have — properties derived from the actual failure modes of the DoD's existing software estate. It requires a module system with explicit separation of interface and implementation. It requires strong, static typing with no implicit conversions between types. It requires built-in support for concurrent tasks. It requires a consistent exception-handling mechanism. It requires that the language be machine-independent. It requires that programs be readable by people other than their authors. It requires that the language make program verification tractable. These were not aspirational preferences. They were requirements derived from the observed consequences of programs that lacked them.2

In 1979, a competition among four finalists — teams designated Green, Red, Blue, and Yellow — produced a winner: the Green design, by a team led by Jean Ichbiah at CII Honeywell Bull in France. The winning design was named Ada, after Augusta Ada King, Countess of Lovelace, the nineteenth-century mathematician who wrote what is generally considered the first algorithm intended for mechanical computation. The choice of name was deliberate: the DoD wanted a name rather than an acronym, wanted to honour a woman in a field that had few women celebrated in it, and wanted to signal that the language was a statement of intent rather than a committee compromise. Ichbiah took the assignment seriously enough to accompany the standard with a rationale document — a full explanation of every design decision and the reasoning behind it — which is still, for anyone who reads it, one of the most lucid accounts in existence of what programming language design is actually for.

· · ·

The centre of Ada's architecture is the package: a compilation unit consisting of a specification and a body, physically separate, with a relationship between them that the compiler enforces. The specification is the contract — it declares what the package provides: types, subprograms, constants, whatever the package makes available to the world. The body is the implementation — it provides the code that fulfills the contract. The specification is what client code sees. The body is invisible to client code and can be compiled independently, changed without recompilation of anything that depends only on the specification, and replaced entirely without any client knowing or caring. This separation is not a style recommendation. It is not enforced by a linter. It is a structural property of the language: client code that attempts to access anything not declared in the specification will not compile, because the compiler will not permit it to see the body.3

This is the module system that every language that came after Ada has been trying to build. Java's packages are not this: they are namespacing mechanisms with access modifiers, but the implementation is visible to reflection, to subclasses, and to code within the same package that may not have been anticipated. Python's modules are not this: they are files, with no formal separation between interface and implementation, no compiler to enforce the boundary. JavaScript's module system — introduced in 2015, thirty-two years after Ada's — provides import and export but no mechanism for a type to have a specification whose representation is hidden from importers. C's header files approximate the separation but without a compiler that can verify consistency between the header and the implementation or prevent the implementation's details from leaking through preprocessor macros. Go's exported identifiers — capitalised names are visible, lowercase names are not — achieve a related effect but without the formal specification-body distinction. Rust's module system with pub visibility rules is again an approximation. None of these is quite Ada's package system, because none of them makes the separation as structurally complete: in Ada, the implementation of a private type is not merely inaccessible, it is syntactically absent from the client's view of the world. It does not exist, as far as the client is concerned. There is nothing to access, reflect on, or circumvent.

Ada's package specification is not a convention. It is a contract enforced by a compiler that refuses to let the client know the implementation exists.

The private type mechanism, which flows naturally from the package architecture, gives Ada something that took every other language decades to approximate. A type declared private in an Ada package specification is visible by name — client code can declare variables of that type, pass them to subprograms, return them from functions — but its representation is completely opaque. The client does not know whether the type is a record, an array, an integer, a pointer, or any other thing. It cannot access fields, because it does not know there are fields. It cannot copy the value in ways the designer did not intend, because it does not know how the value is laid out. The designer of the package decides what operations exist on the type, declares them in the specification, and the rest of the world uses only those operations. This is not access control in the sense of Java's private keyword, which prevents direct access while leaving the representation visible to inheritance, to reflection, and to the compiler itself when it checks subclass compatibility. It is representational invisibility: the type's structure literally does not appear in the text that client code compiles against.

C# spent the better part of its existence providing access modifiers and then slowly building toward genuine encapsulation through mechanisms like record types, init-only properties, and sealed classes. Java's evolution toward genuine data hiding has been similar: records arrived in Java 16, in 2021, providing a class form whose representation is fixed and whose accessors are generated — thirty-eight years after Ada made representational hiding the default for any type declared private. The journey of object-oriented languages toward Ada's package system is the journey of people who were told that access modifiers were encapsulation, discovering gradually that they were not, and rebuilding from scratch what Ada had provided from the beginning.

· · ·

Ada's type system was, in 1983, unlike anything else in production use, and remains, in its essentials, more expressive than most languages that exist today. The distinction that organises it is between a type and a subtype — not in the object-oriented sense of a type that extends another, but in the mathematical sense of a constrained set. An Ada programmer who needs a type representing the age of a person does not reach for int and add a comment. They write type Age is range 0 .. 150, and the compiler generates, without further instruction, a type whose values must lie in that range, whose arithmetic operations are checked against that range at runtime unless the programmer opts into unchecked operations explicitly, and which is a distinct type from every other integer type in the program, so that passing a calendar year where an age is expected is a compile-time error rather than a runtime surprise or a silent wrong answer.4

This was not incremental. In the landscape of 1983, C had int and short and long, distinguished by size and signedness but not by meaning. Fortran had INTEGER and REAL. Pascal had ordinal subtypes but not named distinct types with semantic constraints. Ada's range types, enumeration types, and fixed-point types gave the programmer the ability to encode meaning directly in the type system — to make the type be a machine-checked specification of what the value may be. Rust's u8, i32, u64 are size-and-signedness distinctions that prevent some errors; Ada's range types are semantic constraints that prevent different, more domain-specific errors. Haskell's newtype wrapping provides a closely related mechanism, reaching Ada's destination via a different route. TypeScript's branded types — a workaround pattern involving phantom type parameters, widely used precisely because TypeScript's structural type system otherwise collapses all integers together — exist to solve the problem that Ada named and solved in 1983.

Ada's discriminated record types are more significant still. A discriminated record is a record type with a variant field — a field whose value determines what other fields exist. A shape might have a discriminant selecting between circle and rectangle; a circle has a radius field; a rectangle has width and height fields; the compiler knows which fields exist for which discriminant value and will not compile code that accesses a rectangle's radius. This is the algebraic data type, the sum type, the tagged union — the mechanism that functional programmers have been advocating for decades as the correct way to model data that can be one of several things. Haskell has it as the core of its type system. Rust's enum with data fields is precisely a discriminated union, implemented with the same compiler guarantees Ada provided. Swift has associated value enums for the same reason. Kotlin has sealed classes. TypeScript has discriminated union types, added in version 2.0 in 2016. Ada had discriminated record types in 1983, with compiler-enforced field access checks and the ability to use them as discriminants of other types, forming structures of arbitrary complexity. Every language that has added sum types in the past twenty years has added, with its own syntax, what Ada's designers put in the original standard.

Ada's discriminated record is the algebraic data type. Every language that has added sum types in the past twenty years has independently re-arrived at a 1983 design decision.
· · ·

Ada's generic units are, of the language's many contributions, perhaps the one whose influence is most direct and most consistently unacknowledged. A generic in Ada is a parameterised package or subprogram — a template that can be instantiated with specific types or values to produce a concrete package or subprogram. A generic sort procedure takes a type parameter, an array type parameter, and a comparison function parameter; it can be instantiated to sort integers, or strings, or any type for which a comparison function can be supplied. This is parametric polymorphism: the ability to write code once and apply it to many types, with the compiler verifying correctness for each instantiation rather than deferring the check to runtime or relying on duck typing. Ada had this in 1983.

C++ had templates from approximately 1990. Java had no generics until 2004 — twenty-one years after Ada — and when Java's generics arrived they were implemented through type erasure, which means the type parameters exist at compile time but are removed before the program runs, preventing the kind of runtime type specialisation that Ada's generics make available. C# got generics in 2005 with a more complete implementation that preserves type information at runtime — closer to Ada, but twenty-two years later. Go had no generics at all until version 1.18 in 2022 — thirty-nine years after Ada — and their absence was widely experienced as a significant limitation during Go's first decade of use. Rust has generics with monomorphisation: each instantiation of a generic type produces a concrete type at compile time, the same approach Ada takes. The design space that Rust's generics explore was charted in Ada's standard of 1983.5

Ada's generic formal parameters are more expressive than most modern generic systems. A generic unit in Ada can take as parameters not just types but subprograms — you can pass a function as a formal parameter to a generic and have the compiler verify that it has the right signature — and packages, allowing a generic to be parameterised by a whole module rather than just a type. This is higher-kinded polymorphism by another route: the ability to abstract over not just values but over type constructors and module structures. Haskell's type classes reach a similar expressive power by a different mechanism. Rust's trait system approaches it. C++ concepts, added in C++20 in 2020, allow generic type parameters to be constrained by requirements on their operations — which is what Ada's generic formal type parameters have always specified. The forty-year gap between Ada's feature and C++'s adoption of the same idea is not unusual in this story.

· · ·

Ada's concurrency model is where the gap between what Ada designed and what the industry accepted becomes most consequential, because the industry's failure to accept Ada's model is the direct cause of the concurrency crisis that the industry spent the 2000s and 2010s attempting to resolve. The crisis — shared mutable state made catastrophic by multicore processors, lock-based synchronisation producing deadlocks and race conditions that testing could not reliably detect — was not unforeseeable. It was foreseen, specifically, by the designers of Ada, who designed around it in 1983 and produced, in Ada 95, a concurrency model that subsequent languages have been approximating ever since.

Ada tasks are language-level constructs: declared with task, scheduled by the Ada runtime, communicating through either rendezvous or protected objects. The rendezvous is a synchronised communication point: a calling task names an entry it wishes to use, an accepting task names the same entry in an accept statement, and neither can proceed until both are ready. The communication happens at the meeting; the tasks never share memory implicitly; the calling task cannot reach into the accepting task and modify its state, because the communication model provides no mechanism for doing so. This is message passing — not in the sense that a value is serialised and sent over a socket, but in the sense that the design of the communication prevents shared-state access by construction. Go's channels are a direct instantiation of this idea with different syntax and a slightly different semantics. The Go designers arrived at channels by thinking carefully about concurrency safety; Ada's designers arrived at rendezvous by the same route, thirty years earlier.6

Ada 95's protected objects address the cases where shared state is genuinely required. A protected type wraps data and declares operations on it: protected procedures, which have exclusive read-write access; protected functions, which may be called concurrently because they are read-only; and protected entries, which are like procedures but include a barrier condition — a boolean expression that must be true for the operation to proceed, with the calling task suspended automatically until the condition is satisfied. The runtime enforces mutual exclusion for procedures and entries without the programmer writing a lock. The barrier condition for entries is re-evaluated whenever any operation completes, providing a safe conditional wait without the manual condition variable signalling that Java's concurrency model requires. Rust's Mutex and RwLock types protect data in a related way — wrapping state in a type that enforces access discipline — but through a library rather than a language construct, and without the barrier condition mechanism. Java's synchronized, wait, and notify are what programmers reach for instead, and the combination is an invitation to subtle errors: forgetting to synchronise, notifying the wrong condition, holding a lock while calling foreign code. Ada's protected objects make these errors structurally unavailable rather than merely discouraged.

The SPARK subset of Ada extends the concurrency guarantees to formal proof. SPARK excludes aliasing between task-accessible state, constrains side effects in subprograms to those declared in the subprogram's contract, and provides a static analysis toolchain that can prove, mathematically rather than empirically, that a program has no data races, no unhandled exceptions, no out-of-bounds array accesses, and no violations of stated preconditions and postconditions. Rust's borrow checker prevents a class of memory safety errors at compile time, which is a related but narrower guarantee: it prevents use-after-free, double-free, and certain kinds of aliased mutation, but it does not formally prove the program's logic correct. SPARK proves both the memory safety and the logic. The space between Rust's compile-time rejection of unsafe programs and SPARK's formal proof of correct programs is the space between engineering discipline and mathematical verification — and SPARK has occupied the latter position, in production systems, since before Rust existed as a project.7

Go's channels and Ada's rendezvous are close relatives in the broader CSP tradition. Rust's borrow checker prevents a subset of what SPARK proves. The industry spent thirty years converging toward positions Ada had staked out from the start.
· · ·

Ada 2012 added contracts to the language: preconditions, postconditions, and type invariants, expressible in Ada's own syntax and checked by the compiler or by the runtime at the programmer's direction. A subprogram's precondition is a boolean expression that must hold when the subprogram is called; its postcondition is a boolean expression that must hold when it returns; a type invariant is a property that must hold for every value of a type whenever that value is visible to outside code. These are not assertions in the sense of runtime checks that may be disabled in production. They are specifications: machine-readable statements of what a subprogram requires and guarantees, which can be verified by the SPARK toolchain without executing the program at all.8

Design by contract — the idea, named and systematised by Bertrand Meyer in the Eiffel language in 1986 — is the conceptual foundation of this mechanism. Eiffel had it first; Ada 2012 formalised it in a language with a large existing user base, a formal standard, and a verification toolchain capable of using the contracts for static proof rather than merely runtime checking. The idea's trajectory through the wider industry has been slow. C++ has no standard contract mechanism despite proposals dating to the early 2010s; C++20 deferred a contracts proposal that had been in preparation for years. Java has never had contracts in the language; DbC in Java is done through libraries, or through Javadoc conventions, or through JUnit tests that approximate the postcondition check. Python's type hint system, introduced in version 3.5 in 2015 and progressively extended since, is a partial approach to contracts: it specifies types of inputs and outputs but not behavioural properties. Rust's trait bounds and type constraints are another partial approach. None of these provides what Ada 2012 provides: a standard, compiler-integrated notation for stating what a subprogram requires and guarantees, checkable at runtime during development and provable statically by a toolchain that ships with the language.

The direction of travel in every major language is toward contracts. TypeScript's type system grows more expressive with each release, adding conditional types, template literal types, and increasingly fine-grained narrowing — all of which are approximations of what a contract-capable type system can express directly. Python's typing module grows with each version, adding protocols, TypedDict, ParamSpec, and Concatenate — building, incrementally, toward the kind of interface specification that Ada has had since 1983. C#'s nullable reference types, added in version 8.0 in 2019, impose a constraint that Ada's access type design imposed from the beginning: references must be explicitly declared nullable to permit the null value, and the compiler enforces the distinction. The nullable reference crisis — null as the billion-dollar mistake, Tony Hoare's self-described worst design error — is a crisis that Ada mitigated but did not solve. Ada's access types are initialised to null by default, and dereferencing a null access value raises Constraint_Error at runtime — a defined behaviour, unlike C's undefined behaviour on null dereference, but a runtime check rather than a compile-time guarantee. Ada 2005 introduced not null access type annotations, allowing the programmer to declare that a particular access value may never be null and having the compiler enforce the restriction statically. This is genuine compile-time null safety, but it is opt-in, added twenty-two years after the original standard, and not the default. C#'s nullable reference types, added in version 8.0 in 2019, take the same opt-in approach from the opposite direction: references are assumed non-null unless explicitly annotated as nullable. Both languages arrived at the same architectural insight — that nullability should be visible in the type — but neither made it the default from the start, and neither can claim to have eliminated the problem that Hoare identified. What Ada provided from the beginning was the safer failure mode: a raised exception rather than corrupted memory.

· · ·

The exception handling model that Ada introduced in 1983 was the first production realisation of structured exception handling — the idea that exceptions are not simply jumps to an error handler but events that are raised, propagated through a defined call stack, and handled in an exception handler that is syntactically associated with the block or subprogram that established it. Ada's model requires that exceptions be declared before use, that handlers be associated with specific scopes, and that the propagation rules be defined precisely. C++ adopted structured exception handling in 1990, seven years after Ada. Java went further than Ada in one significant respect: Java's checked exceptions require that certain exception types be either caught or declared in the method's throws clause, making the caller's responsibility for handling failure part of the function's compiled interface. Ada has no equivalent mechanism — Ada exceptions propagate freely through the call stack, and a subprogram's specification says nothing about which exceptions it may raise. Java's checked exceptions drew less from Ada than from CLU's signalling mechanism and Modula-3's exception declarations, and the experiment was contentious from the start: checked exceptions are widely considered one of Java's design missteps, Scala and Kotlin removed them entirely, and the industry has never settled the question of whether the compiler should enforce exception awareness at the call site.9

Rust makes the related choice of removing exceptions entirely: errors are values, returned from functions in a Result type, and the question of whether a function can fail is expressed in its return type rather than in a separate exception mechanism. This is a different resolution of the same underlying problem — that callers must know whether a called function can fail and in what ways — and it reaches a conclusion that Ada's own exception model does not reach: in Ada, as in C++ and Python, exceptions are a hidden channel, propagating through the call stack without appearing in the subprogram's specification, and a caller can forget about them entirely until they arrive. Rust's error-as-value approach and Java's checked exceptions are two different attempts to close that channel. Ada's contribution was not to close it but to structure it — to replace the raw jump of setjmp/longjmp and the ambiguity of signal handlers with a mechanism whose propagation rules were defined, whose handlers were scoped, and whose behaviour was predictable. That structuring was the foundation on which every subsequent exception system was built, even the systems that went further than Ada was willing to go.

· · ·

Ada's annexes — the optional extensions to the core language, defined in the standard, requiring separate compiler certification — represent a design decision that no other language has replicated and that the industry might have benefited from considering. The annexes define features for specific domains: real-time systems, distributed systems, information systems, numerics, safety and security, high-integrity systems. A compiler that implements Annex C for systems programming must implement certain predefined attributes and representation clauses. A compiler that implements Annex D for real-time systems must implement task priorities, scheduling policies, and time constraints in ways the standard specifies. The certification that a compiler conforms to an annex is independently verifiable. The user of a compiler knows precisely what it supports and does not support, because the support is a documented, testable claim against a formal standard rather than an emergent property of whatever the compiler's authors chose to implement.10

No other mainstream language has this model. JavaScript's feature support is tracked through compatibility tables because the standard and the implementation are separate worlds with no formal coupling. Python's standard library coverage varies between implementations — CPython, PyPy, and MicroPython are different things that call themselves Python. Rust's feature set is formally stable or unstable, but the boundary between the two moves over time and the notion of certifiable compliance does not exist. C++ compilers compete on which features of the latest standard they have implemented rather than on certified compliance with any defined subset. Ada's annex model is the idea that a standard should be a contract — testable, certifiable, useful precisely because it specifies not just what is permitted but what is required. The DO-178C standard for airborne software certification, which governs the software in every certified civil aircraft, requires documentation and process evidence that a formally standardised language with certifiable compiler conformance makes considerably easier to produce. Ada's standard, with its annex structure and conformance testing scheme, fits DO-178C's requirements with unusual directness. C and C++ can meet the same certification requirements — and do, routinely — but through additional process documentation and tooling rather than through a standard that was designed with certification in mind. Ada's standardisation and tooling make it unusually well suited to certification-heavy domains; they do not make it the only language that can operate in them.

· · ·

The question of why Ada's influence is so consistently unacknowledged has several answers, none of them fully satisfying. The most straightforward is institutional: Ada was a government language, procured through a process that Silicon Valley was not watching and would not have respected if it had been. The designers of C++, Java, and Python were not reading the Steelman document. They were solving the problems in front of them — making C safer, making software objects work, making scripting simple — and their solutions converged on Ada's solutions not because they were following Ada but because the problems were the same problems and the good solutions are the good solutions.

A second answer is aesthetic. Ada's syntax is verbose in a way that programmers with a background in C find unpleasant. if X then Y; end if; instead of if (x) { y; }. procedure Sort (A : in out Array_Type) instead of void sort(int* a). The verbosity was deliberate — Ichbiah wanted programs to be readable by people other than their authors, and readability over time favours explicitness — but it was experienced as bureaucratic and un-hacker-like, and the programming culture that formed in the 1980s and 1990s was organised around the proposition that conciseness was sophistication. Ada was the language of procurement officers. C was the language of people who understood machines. The cultural verdict was delivered early and never substantially revisited.

A third answer is that Ada's deployment domain meant that Ada's successes were invisible. A software project that compiles without error, runs without race conditions, and has been formally verified to satisfy its specification does not generate incident reports or post-mortems or conference talks about what went wrong. Ada's successes — the aircraft that have not crashed, the railway signalling systems that have not failed, the missile guidance software that has not misguided — are invisible precisely because they are successes. The languages that failed visibly, in buffer overflows and null pointer exceptions and data races and security vulnerabilities, generated the discourse. Ada generated reliable software, and reliable software does not generate discourse.

Ada's successes are invisible because they are successes. The languages that failed visibly generated the discourse. Reliable software does not generate conference talks.
· · ·

The trajectory of modern language design is, traced carefully, a trajectory toward positions Ada occupied early. The type system features that Rust, Haskell, TypeScript, and Swift are celebrated for — sum types, parametric polymorphism, constraint-based generics, affine types and ownership — each solve a problem that Ada identified in 1983 and that the mainstream languages of the subsequent twenty years declined to solve. The module systems that Go, Rust, and Swift have been praised for — explicit interfaces, separation of specification from implementation, visibility control that the compiler enforces rather than merely recommends — are partial implementations of what Ada's package system provided from the beginning. The concurrency models that Go's channels and Rust's ownership have been credited with inventing belong to the same CSP and message-passing lineage as Ada's rendezvous and protected object model, which provided production-grade answers to the same problems in 1983 and 1995. The contract systems that C#'s nullable references, TypeScript's type narrowing, and Python's gradual typing are approximating, from different angles, are what Ada 2012 added to a language that has been in continuous use since before most of its practitioners were born.

This is not a claim that every modern language copied Ada, or that Ada's designers deserve credit that has been withheld from them. Most of the convergence is genuinely independent: the designers of Rust did not derive the borrow checker from Ada's access type rules; the designers of Go did not derive channels from Ada's rendezvous; the designers of TypeScript did not derive discriminated unions from Ada's variant records. The convergence is real but it is convergence toward correct solutions to real problems, not plagiarism. Ada's designers identified the problems first, and identified them with unusual clarity, because they were designing for a context in which the problems had already killed people and would kill more if the solutions were wrong.

What Ada demonstrates is not that it should be more widely used — though the argument for its use in any domain where software reliability matters is stronger than the industry credit it receives — but that the problems modern language design is solving are old problems, and that the solutions modern languages are discovering are old solutions. The idea that null references require explicit annotation, that concurrency requires language-level enforcement rather than library-level convention, that interface and implementation should be structurally separated, that type systems should encode domain constraints rather than merely machine representations, that generic code should be verifiable at instantiation time — these are not insights of the 2010s or the 2020s. They are insights of the 1970s and 1980s, formulated in response to software failures whose consequences were concrete enough that the people responsible for preventing them were willing to pay for a language competition that lasted five years.

The industry has spent forty years building languages whose best features converge, independently, on positions Ada staked out decades earlier. It has spent the same forty years describing Ada as irrelevant. The first observation and the second are in tension in a way that the industry has not yet fully acknowledged, and that Ada — deployed in aircraft overhead, in rail signals alongside the tracks, in the guidance systems of spacecraft currently in transit between planets — has not needed to acknowledge, being too busy running correctly to concern itself with the question of whether it is appreciated.

1The DoD software crisis is documented in a series of internal studies from the early 1970s, summarised in the 1974 report Requirements for High Order Computer Programming Languages, which identified 450 languages and dialects in use across DoD systems and estimated that software costs were growing unsustainably. The figure of 450 languages is cited in multiple histories of the Ada development process, including the account in Jean Sammet and Fred Neighbors, "The ADA Programming Language," Communications of the ACM, 1986. The specific cost projections that motivated the competition are detailed in Fisher, David A., "A Common Programming Language for the Department of Defense," 1976.

2The Steelman document — formally "Requirements for High Order Computer Programming Languages (Steelman)," June 1978 — is the final in a series of requirements documents whose earlier versions were named Strawman (1974), Woodenman (1975), Tinman (1976), and Ironman (1977). The documents are available from public archives and repay reading: the evolution from Strawman to Steelman is a record of requirements being refined by experience, and Steelman's final form is notably precise about what it requires and why. The four competing designs submitted in 1979 were evaluated against Steelman's requirements by independent teams. All four passed the first evaluation; Green and Red advanced to the final evaluation; Green was selected. Jean Sammet chaired the High Order Language Working Group that managed the competition.

3Ada's package structure is specified in chapter 7 of the Ada Reference Manual (ARM). The separation of specification and body is enforced at compilation: a package body must be consistent with its specification, and client code compiled against the specification cannot see the body. The private part of a package specification — between the private keyword and the end of the specification — is visible to the compiler when compiling client code (so that the compiler can allocate storage for private type objects) but not semantically available: client code cannot reference anything defined only in the private part. The Ada Rationale (Ichbiah et al., 1979) explains this design as an explicit rejection of the public-field patterns that were common in the systems languages of the era.

4Ada's type system, with its distinction between types and subtypes and its support for range constraints, enumeration types, fixed-point types, and modular types, is described in chapters 3 through 5 of the ARM. The design principle — that a type is a set of values and a set of operations, and that different semantic domains require different types even when their representation is identical — is stated explicitly in the Ada Rationale and attributed partly to the influence of Barbara Liskov's CLU language and partly to the DoD's operational requirement that programs mixing units (feet and meters, pounds and kilograms) be detectable at compile time. The Ariane 5 rocket disaster of 1996, in which a 64-bit floating-point number was converted to a 16-bit integer without range checking, destroying the rocket 37 seconds after launch, occurred in Ada code that used unchecked conversion to bypass Ada's type system. The disaster is sometimes cited as a failure of Ada; it is more accurately a demonstration of what happens when Ada's type checking is explicitly circumvented.

5Ada's generic units are defined in chapter 12 of the ARM. Generic formal parameters may be types (with various degrees of constraint), subprograms, objects, or packages. The ability to pass packages as generic parameters — a form of higher-kinded polymorphism — was present in Ada 83 and has been elaborated in subsequent revisions. Java's decision to implement generics through type erasure — retaining type parameters at the source level but removing them in compiled bytecode — was driven by backwards compatibility concerns and produced a generic system that cannot express operations requiring runtime type information, cannot use primitive types as type parameters, and generates compiler warnings (unchecked cast) when interacting with legacy pre-generic code. C#'s reified generics — which preserve type information at runtime — avoid these limitations; Microsoft's designers were explicit that they had studied Java's approach and chosen a different architecture. Both approaches are less expressive than Ada's generic formal parameters, which can be constrained to types that support specific operations, checked at instantiation time, without the type erasure problem and without the nominal inheritance required by Java's bounded wildcards.

6Ada's task rendezvous model is described in chapter 9 of the ARM. The rendezvous design was influenced by C.A.R. Hoare's Communicating Sequential Processes (CSP) formalism, published in 1978, which provided a mathematical model of task communication through synchronised events. Go's channels are also derived from CSP, with the explicit acknowledgement of Rob Pike, who worked with Hoare's ideas through his development of the Newsqueak and Limbo languages before contributing them to Go. The line of descent from Hoare's CSP through Ada's rendezvous and through Pike's channel model is a single conceptual lineage, which makes the common framing of Go's channels as a new idea a historical compression. Ada's protected objects, added in Ada 95, were designed by Tucker Taft and others to address the cases where shared state is required — cases that the pure rendezvous model handles awkwardly — and drew on earlier work on monitors and concurrent object models.

7SPARK Ada was originally developed by Program Validation Limited (PVL) in the UK and is currently maintained by AdaCore, with the primary static analysis tool being GNATprove. The SPARK language definition is maintained separately from the Ada standard and specifies a subset of Ada from which aliasing, unconstrained side effects, and certain forms of dynamic dispatch are excluded. GNATprove uses abstract interpretation and SMT (satisfiability modulo theories) solvers to prove, without executing the program, that subprograms satisfy their contracts, that array accesses are within bounds, that integer operations do not overflow, and that tasks do not race on shared state. The Airbus A380's primary flight control system, the SHOLIS helicopter system, and the UK's MULTOS smart card operating system are among systems that have been formally verified in SPARK. Rust's formal verification toolsets — Kani, Creusot, and others — are research and production tools that apply similar SMT-based verification to Rust programs; they are newer, less mature, and cover a smaller fraction of Rust's feature set than GNATprove covers of SPARK.

8Ada 2012's contract model is specified in chapter 6.1 (preconditions and postconditions) and 7.3.2 (type invariants) of the ARM. Preconditions and postconditions are boolean expressions written in Ada itself, evaluated on subprogram entry and exit respectively when assertion checking is enabled. SPARK's toolchain uses the same expressions as formal specifications for proof. Bertrand Meyer's Eiffel language introduced the design-by-contract terminology and the concept of invariants, preconditions, and postconditions in the mid-1980s; Ada 2012's contract model is substantially aligned with Eiffel's, though the integration with SPARK's proof system extends the contracts beyond what Eiffel's runtime checking provides. C++20's contracts proposal, which would have added preconditions and postconditions to C++ with similar syntax, was withdrawn from the C++20 standard after committee disagreement about semantics; as of 2024 a revised proposal is under development for a future standard. The forty-year gap between Ada's precursors and C++'s adoption of contracts is, in the history of language features, almost but not quite the longest.

9Ada's exception model is defined in chapter 11 of the ARM. The requirement that exceptions be declared and that their propagation be through a structured call stack — rather than through setjmp/longjmp or signal handlers — was part of the original Ada 83 design. The Steelman document explicitly required "a uniform mechanism for handling run-time errors and other exceptional situations." Ada's exception model does not, however, include any mechanism for declaring which exceptions a subprogram may raise: exceptions propagate freely, and a caller has no compiler-enforced obligation to handle or declare them. Java's checked exceptions — which require that certain exception types in a declared throws clause be either caught or re-declared by callers — went beyond Ada's approach, drawing more directly from the exception mechanisms of CLU and Modula-3. The checked exception model was controversial within Sun from the start, and the subsequent history — checked exceptions widely considered a design mistake, RuntimeException and Error used to circumvent the checking requirement, Scala and Kotlin removing checked exceptions while keeping Java compatibility — is a history of the industry testing and largely rejecting a position that Ada's designers did not take.

10Ada's normative annexes are defined at the end of the ARM: Annex C (Systems Programming), Annex D (Real-Time Systems), Annex E (Distributed Systems), Annex F (Information Systems), Annex G (Numerics), and Annex H (High Integrity Systems). A compiler may claim conformance to any subset of the annexes, with each conformance claim subject to independent validation testing. The Ada Conformity Assessment Authority (ACAA) administers the Ada Conformity Assessment Test Suite (ACATS), a suite of tests that any conforming Ada implementation must pass. The DO-178C standard for airborne software, which governs software in civil aircraft certified by the FAA, EASA, and equivalent authorities, requires documentation and process evidence that higher certification levels (Design Assurance Level A and B) make particularly demanding. Ada's formal standard, with its annex structure and conformance testing scheme, provides evidence of language-level guarantees in a form DO-178C can directly reference. C and C++ are also widely used in DO-178C-certified software, with equivalent certification achieved through additional process documentation and qualified tooling. Ada's advantage in this context is not exclusivity but fit: the language standard was designed, in part, with the needs of certifiers in mind.

ERRATA

Exception handling and Java's checked exceptions. The original version of this essay stated that Java's checked exceptions were "a direct borrowing of Ada's idea that callers should know what exceptions a subprogram may raise," and that Ada had exception specifications "in its subprogram specifications as a standard feature from the beginning." Both claims were wrong. Ada's exception model requires that exceptions be declared as named objects and that handlers be associated with specific scopes, but it does not require — and has never required — that subprograms declare which exceptions they may raise. Ada has no equivalent of Java's throws clause. Exceptions in Ada propagate freely through the call stack; a caller has no compiler-enforced obligation to handle or acknowledge them. Java's checked exception mechanism went beyond what Ada provided, and its intellectual lineage runs more directly through CLU's signalling mechanism and Modula-3's exception declarations than through Ada. The corrected text reflects Ada's actual contribution — the structuring of exception propagation into a defined, scoped, predictable mechanism — without overclaiming that Ada required callers to know what a subprogram might raise. The accompanying footnote (9) and the paragraph on Rust's error-handling model have been revised accordingly.

Access types and null safety. The original version stated that the nullable reference crisis was "a crisis that Ada did not have, because Ada access types are initialised to null by default and the compiler requires explicit null exclusion or null checking before use," and that "C# arrived at enforced null safety thirty-six years after Ada." This materially misrepresented Ada's null-handling semantics. Ada access types do default to null, but dereferencing a null access value is not a compile-time error — it raises Constraint_Error at runtime, which is a defined and recoverable behaviour (unlike C's undefined behaviour on null dereference) but is not compile-time null safety. The not null access type annotation, which does provide compile-time null exclusion, was introduced in Ada 2005, twenty-two years after the original standard, and is opt-in rather than the default. The corrected text describes what Ada actually provides — a safer runtime failure mode from the beginning, and genuine but opt-in compile-time null exclusion from 2005 — without the false claim that Ada solved the null problem from the start.

Epigraphs. Three epigraph quotations attributed to Jean Ichbiah, Bjarne Stroustrup, and Tucker Taft appeared in the original version. None could be traced to a reliable primary or secondary source. They have been removed.

"Invented the generic." The opening paragraph originally described Ada as the language that "invented the generic." Parameterised abstractions existed before Ada, notably in CLU (Liskov et al., 1977). Ada made generics a first-class, standardised feature of a widely deployed systems language, which is a different and more defensible claim. The wording has been revised accordingly, and the opening paragraph's compressed timeline — which implied that all of the essay's headline features shipped in 1983 — has been expanded to acknowledge that protected objects arrived in Ada 95, not null in Ada 2005, and contracts in Ada 2012.

"Go's channels are Ada's rendezvous with different syntax." A pull quote and the trajectory paragraph both characterised Go's channels as a re-implementation of Ada's rendezvous. The more accurate claim is that both belong to the broader CSP/message-passing tradition descending from Hoare's 1978 formalism. Go's own lineage runs explicitly through Newsqueak and Limbo; the family resemblance to Ada is real, but the direct-equivalence framing was too strong. The corrected text describes them as close relatives in a shared lineage.

Aviation and DO-178C. The original text claimed Ada had "a presence in the software of every commercial aircraft currently in service" and that DO-178C effectively treats Ada's formal standard as a prerequisite, implying other languages cannot be used in certified airborne software. Neither claim is supported by the available evidence. Ada is used in many major commercial aircraft and avionics systems, but "every commercial aircraft" is unverifiable and almost certainly false. DO-178C certification is routinely achieved with C and C++ through additional process documentation and qualified tooling, and Rust tooling for certification contexts is under active development. The corrected text describes Ada's standardisation and tooling as unusually well suited to certification-heavy domains without claiming exclusivity. Footnote 10 has been revised accordingly.

Footnote 3: "friend-class" anachronism. The original footnote attributed to the Ada Rationale an explicit rejection of "friend-class and public-field patterns that C was making common." The friend keyword is a C++ feature (Stroustrup, 1985), not a C feature; it did not exist in the language landscape of 1979. The unsourced Ichbiah quotation ("the private part as a structural firewall") has also been removed. The footnote now references the Ada Rationale's rejection of public-field patterns in the systems languages of the era without the anachronistic C++ attribution.

"The language that refuses to compile programs it cannot verify." The original second paragraph described Ada as "the language that refuses to compile programs it cannot verify." This overstates what the Ada compiler enforces. An Ada compiler checks legality, visibility, typing, accessibility, and many safety-related constraints, but full formal verification — proof of absence of runtime errors, data races, and contract violations — is the province of SPARK and GNATprove, not of ordinary Ada compilation. The corrected text describes the compiler's actual enforcement without conflating it with formal proof.