.. /index

Design by Contract

Design by Contract

Design by Contract (DbC) is Bertrand Meyer's methodology, developed in the Eiffel language in the 1980s, of attaching machine-checkable preconditions (what a function requires of its caller), postconditions (what the function guarantees on return), and class invariants (what is always true of a well-formed object) to every public interface. The contracts are first-class language constructs, not comments or assertions, and they participate in inheritance — derived classes can weaken preconditions and strengthen postconditions (the language-level expression of Liskov Substitution). DbC is now arriving in mainstream languages thirty-five years after Eiffel pioneered it: C++ via cpp26-contracts, Ada has had it since 2012, D since inception, and Rust is debating a contracts RFC.

The core triple

A contracted function has three pieces, in addition to its signature:

Together they form the function's contract: under the precondition, the function delivers the postcondition. Caller and callee each have responsibilities; correctness is the conjunction.

The Liskov connection

The most theoretically interesting property of DbC is its formalization of subtyping. In Meyer's framework:

These rules are exactly Barbara Liskov's substitution principle made operational. A Square cannot be a subclass of Rectangle (the classic counter-example) because Square::set_width(w) strengthens the postcondition width == w to also enforce height == w, which is not what Rectangle::set_width's contract promised — and that strengthening of the postcondition implicitly strengthens what the caller can rely on, breaking substitutability.

C++26 contracts inherit this lineage but do not enforce the LSP rules at the language level (that requires the contracts-in-inheritance extension, deferred). For now the discipline is the developer's; the contracts express the intent without enforcing the substitution property.

What DbC is good at

Encoding informal preconditions formally. Every function in every codebase has implicit preconditions ("don't pass null," "must call init() first," "size must be even"). DbC makes them part of the type.

Catching contract violations early. A violated precondition is a programmer error — the caller violated its part of the agreement. DbC makes this an explicit, debuggable event rather than a silent UB or eventual crash.

Driving design discussions. Writing the contracts before the body forces you to articulate what the function actually requires and promises. This is the methodological core of Meyer's book Object-Oriented Software Construction.

Optimizer hints. A precondition is a promise from the caller to the callee; the optimizer can use it as a free assumption when generating code for the callee. pre(p != nullptr) lets the compiler skip null checks throughout the body even in release builds.

What it's not

Proof. A contract is a runtime check (or an assumption — in C++26's terminology, enforce vs ignore semantics). It does not statically prove correctness. For static proof you need a proof assistant like Lean 4 or a dependently-typed language like Idris — see the wiki's pages on those.

Type system. DbC complements types; it does not replace them. Types capture structural properties ("this is an integer"); contracts capture predicates over values ("this integer is positive"). The two are orthogonal axes of specification.

Refinement types. Languages like Liquid Haskell and F* express contract-like constraints as part of the type (type Pos = { x : Int | x > 0 }) and statically verify them. DbC is the dynamic-check analog of the same idea; refinement types are the static-proof version. The two converge in expressiveness but diverge sharply in implementation cost.

Languages where DbC is first-class

Connection to type-driven design

DbC sits in the same family as type-driven-development, parse-dont-validate, and monadic error handling — all of them push specification from comments and conventions into machine-checkable form. The differences:

The right tool depends on what you can express in your type system and what you can afford to verify. In Rust, parse-dont-validate takes you a long way because the type system is expressive enough. In C++, cpp26-contracts cover the cases where the type system runs out. The combination of type-driven-development and contracts — types for structural invariants, contracts for value-level predicates — is the practical answer for most production code.

The slow uptake question

DbC has been broadly understood for thirty-five years and is provably useful, yet adoption outside Eiffel remained marginal until C++26's contracts. Why?

The honest answer: contracts require organizational discipline — every function gets a contract, every contract is reviewed, violations are taken seriously. That's expensive process for an unclear payoff in most codebases. The languages that required contracts (Eiffel) never reached mass adoption; the languages that made them optional (Ada, D) saw them used in domains with already-strong correctness culture (avionics, safety-critical systems).

C++26's bet, and the bet behind the Rust RFC, is that cheap, optional, optimizer-integrated contracts can clear the adoption bar in a way that prior attempts couldn't. The first wave of production data should arrive in 2027–2028; whether the bet pays off is genuinely open.

Linked from

Sources