· 7 min read ·

Ada's Fingerprints on the Languages You Write Today

Source: hackernews

Forty-three years after it was standardized, Ada rarely appears in conversations about modern language design. Rust gets the credit for zero-cost abstractions and fearless concurrency. Java gets credited for structured exception handling. Go gets praised for a clean concurrency model. But a lot of what those languages popularized was already worked out, formally specified, and deployed in flight control software before most of today’s working programmers were born.

This piece on Hacker News is generating attention precisely because it names the pattern directly: Ada was not an obscure academic exercise. It was a production language for high-consequence software that solved problems the broader industry took decades to rediscover.

Where Ada Came From and Why It Was Different

In the mid-1970s, the United States Department of Defense was running hundreds of different programming languages across its systems, with no standardization and no shared tooling. Software failures in defense systems were frequent and expensive. The DoD commissioned a competition to produce a single, reliable, general-purpose language suitable for embedded and real-time systems. The resulting competition ran through several specification documents, culminating in the Steelman requirements from 1978.

Four teams competed, and CII Honeywell Bull, led by Jean Ichbiah, won. The language was named after Ada Lovelace, and the first standard, Ada 83, was ratified by ANSI. Subsequent revisions produced Ada 95 (which added object-oriented programming), Ada 2005, Ada 2012 (which added formal design-by-contract constructs), and Ada 2022.

The design goals are worth stating plainly: reliability, maintainability, efficiency, and the ability to write both high-level application logic and low-level hardware interfaces in the same language. These goals drove specific technical choices that turned out to be prescient.

Range-Constrained Types and the Newtype Pattern

Ada lets you define numeric types that carry range constraints as part of their type, not as runtime assertions:

type Altitude_Meters is range 0 .. 80_000;
type Airspeed_Knots  is range 0 .. 600;

Altitude : Altitude_Meters := 10_000;
Speed    : Airspeed_Knots  := 250;
-- Altitude := Speed;  -- compile error: incompatible types

These are distinct types. You cannot assign one to the other without an explicit conversion, and the conversion itself checks the range at compile time or runtime depending on what is knowable statically. Overflow and out-of-range assignments raise Constraint_Error.

Rust’s newtype pattern achieves something similar, though less concisely:

struct AltitudeMeters(u32);
struct AirspeedKnots(u32);

// These cannot be mixed up without explicit unwrapping and re-wrapping

Rust’s approach requires manual boilerplate for arithmetic and comparisons unless you implement the relevant traits. Ada’s range types are numeric: you can do arithmetic on them directly, and the compiler tracks the arithmetic’s possible range. The type system does more work for you with less ceremony.

Discriminated Records: Tagged Unions Before They Were Trendy

Rust’s enums are one of its most praised features. Haskell’s algebraic data types are a foundation of functional programming. Ada had a structurally equivalent concept in 1983 under the name discriminated records:

type Shape_Kind is (Circle, Rectangle, Triangle);

type Shape (Kind : Shape_Kind) is record
   case Kind is
      when Circle =>
         Radius : Float;
      when Rectangle =>
         Width, Height : Float;
      when Triangle =>
         Base, Height_T : Float;
   end case;
end record;

The discriminant Kind is part of the type. Accessing Radius on a rectangle raises Constraint_Error. The compiler tracks the discriminant and enforces it at case statements. Rust’s equivalent:

enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
    Triangle { base: f64, height: f64 },
}

The conceptual territory is identical. The lineage is not direct, since Rust draws more immediately from ML and Haskell, but Ada demonstrates that by 1983 the industry had a working, deployed, industrial implementation of the idea. The “discovery” in modern systems languages was more of a rediscovery.

The Tasking Model and Go’s Channels

Ada has first-class concurrency built into the language. Not a library, not a standard library add-on: the language itself has tasks, protected objects, and a rendezvous mechanism:

task type Worker is
   entry Submit_Job (J : Job_Record);
   entry Collect_Result (R : out Result_Record);
end Worker;

task body Worker is
   Current_Job : Job_Record;
begin
   accept Submit_Job (J : Job_Record) do
      Current_Job := J;
   end Submit_Job;
   -- do the work
   accept Collect_Result (R : out Result_Record) do
      R := Process (Current_Job);
   end Collect_Result;
end Worker;

The accept statement is a synchronous rendezvous: the calling task blocks until the worker reaches that accept, and they execute the body together before both continuing. This is the same semantics as an unbuffered channel send in Go. You send, the receiver must be waiting, both sides synchronize.

Ada 95 added protected objects for monitor-style mutual exclusion, which maps closely to Go’s sync.Mutex combined with sync.Cond. Ada protected objects include entries with guards, eliminating the polling loops that often appear in Go code waiting for a condition.

Go’s CSP-influenced concurrency is credited to Tony Hoare’s 1978 paper. Ada drew from the same source. What Go marketed as a fresh design philosophy in 2009 was available, specified, and deployable in 1983.

Generics With Explicit Contracts

Ada’s generics require you to specify what properties the type parameter must satisfy, as a formal contract:

generic
   type Element_Type is private;
   with function "<" (Left, Right : Element_Type) return Boolean;
package Ordered_Set is
   procedure Insert (Item : Element_Type);
   function Contains (Item : Element_Type) return Boolean;
end Ordered_Set;

The with function "<" clause is a formal parameter: you must supply a comparison function at instantiation. The generic body can use < on elements, and the instantiation site supplies the actual comparison. There are no implicit interface requirements and no duck typing. Everything is stated explicitly.

C++ templates, by contrast, use substitution: if you pass a type that lacks operator<, you get an error at the instantiation point, not at the generic definition. This was a known problem with C++ templates for decades, producing error messages that referenced internals of the standard library instead of the caller’s mistake. Rust’s trait bounds, introduced in a language that released in 2015, give you explicit contracts at the definition site. Ada had this in 1983.

Java’s generics, added in Java 5 in 2004, use bounded wildcards and type erasure, which is a different approach with known limitations. The direction of travel in language design, from implicit substitution toward explicit contracts, has been toward what Ada already specified.

Design by Contract, Formalized

Ada 2012 added first-class support for pre- and postconditions as language-level aspects, not library annotations:

function Safe_Divide (Numerator, Denominator : Integer) return Integer
  with Pre  => Denominator /= 0,
       Post => Safe_Divide'Result * Denominator = Numerator;

function Binary_Search
  (A     : Integer_Array;
   Value : Integer) return Integer
  with Pre  => A'Length > 0 and then Is_Sorted (A),
       Post => (Binary_Search'Result = -1) or else A (Binary_Search'Result) = Value;

These are checked at runtime in debug builds and can be used by static analysis tools in production. The concept comes from Bertrand Meyer’s Eiffel language, but Ada’s 2012 formalization brought it to a systems programming context with ISO standardization.

Rust achieves related guarantees through its type system rather than runtime assertions: a function that takes NonZeroI32 as the denominator makes the precondition structurally impossible to violate, not just checked at entry. Both approaches are sound; they operate at different levels of the abstraction stack. Ada’s postconditions are expressive in ways Rust’s type system cannot easily encode, particularly for relational properties involving multiple values.

SPARK and the Verification Frontier

Ada’s design anticipated that formal verification would matter. SPARK, a formally defined subset of Ada developed by Altran and AdaCore, takes this further: it excludes features that make formal reasoning difficult (aliasing, unrestricted access types, dynamic dispatch in certain contexts) and provides a proof tool that can verify absence of runtime errors, data races, and arbitrary user-specified properties.

Airbus uses SPARK for flight management systems. The DO-178C standard governing aviation software certifies at the highest assurance level, and SPARK tooling is one of the few practical paths to that certification. This is not a research prototype; it is production aerospace software that carries passengers.

Rust with the Kani model checker or Prusti is working toward comparable guarantees, but the ecosystem is newer and the tool maturity is lower. Ada and SPARK have thirty-plus years of production use in domains where the cost of a memory safety bug is a crash investigation.

Why It Stayed Quiet

Ada’s limited spread outside defense and aerospace comes down to several compounding factors. DoD procurement requirements created a captive market that reduced pressure to compete on developer ergonomics. The early compilers were slow and expensive. The verbose syntax, designed for readability in safety-critical reviews rather than for quick iteration, felt heavy for general application development. And by the time Ada 95 added object-oriented programming, Java was arriving with a simpler story and a massive runtime library ecosystem.

None of these reasons are technical arguments against Ada’s design. They are arguments about market timing, tooling economics, and developer culture. The features that made Ada difficult to adopt in the 1990s, explicit contracts, mandatory type annotations, formal concurrency primitives, are the same features that made Rust attractive in the 2010s when the industry had accumulated enough pain to value them.

The source article earns its title. Ada is a quiet colossus: present in the foundations of modern language design, largely invisible to the programmers whose languages it influenced, and still active in the software running aircraft and rail systems that most of those programmers ride on.

Was this interesting?