Chapter 17 — Contracts

A contract is a statement of what a function promises, written right next to its signature, in ordinary Ingle. There are two halves: requires clauses (preconditions — what must be true when you call it) and ensures clauses (postconditions — what will be true when it returns). An ensures clause may use the special name result for the return value.

fn clamp(x: int, lo: int, hi: int) -> int
    requires lo <= hi             // precondition: checked on entry
    ensures result >= lo          // postconditions: checked before every return,
    ensures result <= hi          // with 'result' bound to the returned value
{
    if x < lo { return lo }
    if x > hi { return hi }
    return x
}

That’s not a comment. It’s executable. In a normal (debug) build, requires is checked the moment the function is entered, and every ensures is checked just before each return. If one fails, the program stops with a message that tells you exactly which clause, in which function:

fn pos(x: int) -> int
    requires x > 0
{
    return x
}

fn main() -> int { return pos(-5) }
inglec: runtime error: precondition failed in 'pos' (requires, line 1)

A contract is just a bool expression, so the full language is available — including calling your own predicate functions. You can write requires is_sorted(xs) and define is_sorted normally. (Contracts should read their inputs, not change them.) Here’s the shipped examples/07_contracts.ig, which specifies Euclid’s GCD precisely — the result is positive and divides both inputs:

fn gcd(a: int, b: int) -> int
    requires a > 0
    requires b > 0
    ensures result > 0
    ensures a % result == 0
    ensures b % result == 0
{
    var x = a
    var y = b
    loop {
        if y == 0 { return x }
        let t = x % y
        x = y
        y = t
    }
}

Those three ensures clauses are a real, checkable specification of “is a common divisor,” not a vague comment hoping to stay true.

assert, for checks inside a function

For an invariant that should hold mid-function rather than at its boundary, there’s assert(cond) (or assert(cond, "message")). It lowers to the same machinery as a contract, so a failure is the same kind of structured event, not a bare crash.

The bit that makes it free in production

Contracts are checked in debug builds — the default. A --release build elides them entirely: zero runtime cost. So you get correctness-checking while you develop and pay nothing when you ship. Watch the same broken contract simply vanish under --release:

inglec --emit=run           bad.ig     # precondition failed in 'pos' ...
inglec --emit=run --release bad.ig     # => 6   (the check is gone)

This is the proven debug_assert model: the safety net is up the whole time you’re working, and folded away for the performance run. (Type-checking always happens, in every build — a contract that isn’t a bool is a compile error no matter the profile. Only the runtime check is what --release drops.)

Fireside trivia. Design by contract dates to Bertrand Meyer and the Eiffel language in the 1980s, named by analogy with business contracts: the caller promises the precondition, the function promises the postcondition, and if both keep their word the program is correct. The idea spent decades admired-but-niche. What’s new in Ingle isn’t contracts themselves — it’s fusing them with a machine-readable trace and automatic testing, which is the next chapter, and the whole reason they’re here.