C++26 gets contracts. The man who built them into D says they weren't useful

C++26 adds pre(...), post(...) and contract_assert(...), but the standard lets each compiler decide whether a violated contract is ignored, logged, or kills the program.

In brief

Contracts let a C++ function state what callers must guarantee and what it promises back. A violation can be ignored, observed, enforced or quick-enforced, chosen per build with no portable per-assertion switch, and the guide warns they are not input validation.

Walter Bright, who says he added contracts to Zortech C++ in the 1990s and to D later, wrote that they "just turned out to not be very useful". An embedded developer wants the opposite trade: checks on at the workstation, off on weak production CPUs.

Three keywords and four outcomes

The C++ Stories guide walks through the feature. pre(...) goes after the parameter list and states what the caller must guarantee on entry. post(result: ...) states what the function promises on normal return, and one postcondition covers every return path. contract_assert(...) checks a property at a point inside a body. Contracts are not part of the function type, so overload resolution ignores them.

Contracts illustration from the C++ Stories article
Contracts illustration from the C++ Stories article

What happens on a violation depends on the evaluation semantic. Under ignore the predicate is not evaluated. Under observe a violation calls the handler and execution continues if it returns. Enforce calls the handler and terminates if it returns, and quick-enforce terminates without calling the handler. The implementation decides which semantics it supports, and C++26 has no portable way to choose one for an individual assertion. GCC 16 compiles them with -std=c++26 -fcontracts -fcontract-evaluation-semantic=enforce.

The guide lists the traps. Predicates should not change state, but nothing stops one from calling a function that does. Postconditions are not checked when the function exits by throwing. Under observe or ignore, the invalid operation can still run into undefined behavior. And expensive predicates, such as checking that a whole collection is sorted, can slow programs down.

Bright says contracts were not very useful, and practitioners disagree

"I used to be a big fan of contracts, and even implemented it in the Zortech C++ compiler back in the 90's… I implemented it in D as well. But, it just turned out to not be very useful." — Walter Bright, who says he built contracts into both Zortech C++ and D

A reply asked the obvious follow-up: why not useful, if done right they should push many cases to compile time and feed the optimizer? Elsewhere in the thread, people who use the idea day to day described what they get. A commenter who works in embedded answered a skeptic of runtime checks:

"I work in embedded, in production our CPUs are not powerful enough to risk all those runtime checks, but I want to turn them on when I work on my workstation - in hopes that if there is a problem we catch it." — embedded developer replying to a skeptic of runtime asserts

A commenter with 19k karma added that pre and post conditions in Clojure are "a small but meaningful improvement over plain old assertions", and that being able to turn them off is "a feature not a bug". The thread does not record Bright's reasons, but these commenters show what people still using the idea say they get from it.

"Muddy" versus "the blurriness predates contracts"

A commenter with 22k karma found the guide's own warning the problem: contracts are not input validation, not parameter validation, "checks which error out, but it's not errors we are producing". They also wanted contracts that turn runtime errors into compile-time errors, and thought what the standard offers is an assert that they "promise to really assert (probably)".

Two replies pulled the argument apart from different sides. A commenter with 21k karma said the blur predates contracts, since the same distinction applies to assertions:

"an assertion failure (and therefore a contract failure) indicates a bug. It must never happen, and therefore it must always be OK to suppress the validation." — commenter with 21k karma

A commenter who has used Clojure's pre and post conditions answered the compile-time wish: forcing these conditions into compile-time checks would introduce a second type system with refinement types, and a precondition like collatz(x)==1 would ask the compiler to prove the Collatz conjecture. A third phrasing, from a commenter with 33k karma, drew the line by possibility: "If it can happen, but shouldn't, that's input validation", while "if it can't happen and shouldn't, that's a contract".

The case that contracts can be less safe than assert

The strongest attack on the feature itself came from a commenter who walked through the spec's "ghost code" model. Per the spec, you must never assume a contract check actually runs, even when you asked the compiler to run it, because ABI and ODR (one-definition rule) problems can delete the check. The consequence they drew:

"People mistakenly transform #1 into #2 unconditionally, not realising that this is actually a misuse of the contracts feature. It can, and will lead to undefined behaviour" — commenter walking through the spec's ghost-code model

Here #1 is assert(p != nullptr); assert(p->some_value()); and #2 swaps in contract_assert for both: the second statement may then dereference a null pointer if the first check never ran. They predicted that in ten years the advice will be to avoid contracts in safety-relevant code. Set beside the embedded developer, it is one feature read two ways: a development-time net for one, a hazard for anyone who cannot be sure what a given build does with it.

Where they go, and whether the optimizer may believe them

A reader complained that putting contracts in headers drags in "half the world" of includes. A commenter replying to them said the first declaration must carry the conditions, and they can call out to functions elsewhere for checks needing internal scope. Another suggested modules, and a third that the answers will come in a few years from someone who has retrofitted a real codebase.

The optimizer question split on facts. A developer who wrote their own C compiler said compilers will optimize on the assumption that contracts hold: "Of course, that's the idea." A software developer with a physics PhD said that "is not a primary goal", and that "assume" semantics were discussed but are not among the standardized semantics. If the second answer is right, the free performance information a commenter asked about is not part of what C++26 standardizes.

Get breakdowns like this every day

The top Hacker News stories, unpacked — by email, Telegram, or RSS.

Next story · 10 of 13 Read more
🚀Tech

A fake South Park episode in Sonarr was an 887 MB infostealer

The torrent had 2,392 seeders and looked like a normal release. Sonarr could not import it, so it retried every 90 seconds while qBittorrent reported it finished with 0 bytes downloaded.

★ 331 · 💬 146 · thereallo.dev · 3 min