digitalmars.D - Maybe D was wrong on contracts
- Quirin Schroll (75/91) Aug 17 TL;DR: D’s contracts are theoretically unsound, but there’s an
- Richard (Rikki) Andrew Cattermole (10/10) Aug 17 Two known problems with contracts:
- Timon Gehr (4/18) Aug 17 Continuing when an `in` contract fails needs to catch an `AssertError`.
- Meta (3/5) Aug 18 What's the best solution to this? Changing the throwing of an
- H. S. Teoh (15/24) Aug 17 [...]
- Jonathan M Davis (54/75) Aug 20 100%, though I don't think that contracts get used much in D ever withou...
TL;DR: D’s contracts are theoretically unsound, but there’s an easy fix. --- I just read [P3097](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3097r3.pdf), an accepted proposal for C++29. It allows for pre- and postconditions on virtual member functions. Its design goals and discussion sections (§§ 3–4) are really interesting as they discuss (among others) the design of D’s contracts. (You don’t need to know any C++ to follow most of the proposal sections.) Here’s the, in my opinion, most crucial paragraph for the D community. “It” refers to the design of prior proposals that were similar to D’s and Eiffel’s contracts, which closely follow the Liskov substitution principle:[It] assumes that contract assertions can express the entire plain-language contract across the whole program, while in reality they will only ever express a subset of it, and only in some components. Our proposed C++ design accounts for this fundamental limitation, ensures that introducing contract assertions cannot cause remote breakage of correct code, and enables the user to adopt precondition and postcondition assertions on virtual functions incrementally. […] More fundamentally, OR-ing two sequences of *contract assertions* is not equivalent to OR-ing the two contracts that those assertions check. As we saw above, contract assertions can only validate a subset of a function’s plain-language contract. Therefore, assuming that one side of such a disjunction is satisfied merely because none of the associated assertions failed is logically unsound.In § 4.7, P3097 exemplifies how the mantra of OR-ing and AND-ing is incorrect (overly narrow) if the goal is to enforce the substitution principle. I won’t bore you with the example; in short: If a virtual function has a precondition *P* and postcondition *C* (read *C* as conclusion), any implementation must satisfy the implication *P* ⇒ *C.* Thus, for the substitution principle to hold, any overriding function must also satisfy *P* ⇒ *C,* but the current state of the D language requires it to actually satisfy *P* ∨ *Q* ⇒ *C,* where *Q* is the conjunction of the preconditions of the overrider. Notably, *Q* might be `true` when the preconditions of the overrider are the empty set. Maybe C++ got something right, or rather, D got something wrong that C++ got right-er: *A virtual function’s postconditions don’t get limit what overriders do when invoked with arguments that don’t satisfy its preconditions.* The violation of this principle might be the reason why D’s contracts never took off. It might be worth considering moving closer to C++’s semantics of contracts, not because of interoperability or compatibility, but because they’re closer to correct. D should validate contracts differently and in-line with the following principle: *The postconditions of a function need only be met by its implementation if its precondition was satisfied.* For non-virtual functions, the violation of preconditions simply leads to an `Error` being thrown, thus skipping the code that checks the postconditions, thus coincidentally satisfying the principle. It fails only for virtual functions. Example: ```d class Base { int f(int x) in(x > 0) out(r; r > 0); } class Derived : Base { override int f(int x) in(true) out(r; true ); } ``` If we assume both implementations are just `{ return x; }`, all contracts should be satisfied because with `r == x`, all individual contracts are of the form *X* implies *X,* which is obviously true. Where D is going astray is not that when calling `Derived.f`, it checks if `Base.f.in` is satisfied at all, but what it does with the result. Current behavior is: `Derived.f` cannot assume anything (because `Derived.f.in` is `true`) and must definitively establish `r > 0` because `Base.f.out` says so and in the current language semantics, all postconditions have to be met unconditionally. This is logically incorrect because `Base.f.out` need only be satisfied if `Base.f.in` was satisfied to begin with (that’s what a contract actually means); if it wasn’t satisfied, `Derived.f` should not be bound by `Base.f.out` in any way. I have no idea how DMD implements contract checking, but I assume it wouldn’t be too difficult to skip checking postconditions if the corresponding preconditions hadn’t been met. At worst, it has to store a `bool` to remember that. I’d consider this a miniscule price to pay (in terms of performance) for a contract semantics that is theoretically sound. C++29, according to the proposal, given an object with a static type of `C`, the language wouldn’t consider the contracts of `Base.f` at all. The reasoning is: If we *know for sure* we have a `C` object, only contracts specified on `C` concern us, as well as contracts of the dynamically invoked function; this (intentionally) allows for violations of the substitution principle for cases where no plain substitution takes place. I’m not proposing that at all, or rather, if anything, for `extern(C++)` functions only.
Aug 17
Two known problems with contracts: 1. =inclusiveincontracts 'in' contracts of overridden methods must be a superset of parent contract (https://dlang.org/changelog/2.095.0.html#inclusive-incontracts) 2. the decision to emit must be done by the caller, not the callee's compilation flags. Nobody has wanted to fix this yet (although approved). To quote Mike: "We approved enabling the following switches in a future edition: fieldwise, nosharedaccess, inclusiveincontracts, fiximmutableconv, and systemvariables."
Aug 17
On 8/17/26 20:57, Quirin Schroll wrote:TL;DR: D’s contracts are theoretically unsound, but there’s an easy fix. .... Maybe C++ got something right, or rather, D got something wrong that C++ got right-er: ....It's not like I didn't try: https://issues.dlang.org/bugs/7584/... I have no idea how DMD implements contract checking, but I assume it wouldn’t be too difficult to skip checking postconditions if the corresponding preconditions hadn’t been met. At worst, it has to store a `bool` to remember that. I’d consider this a miniscule price to pay (in terms of performance) for a contract semantics that is theoretically sound. ...Continuing when an `in` contract fails needs to catch an `AssertError`. (This is already a problem.)
Aug 17
On Monday, 17 August 2026 at 19:10:57 UTC, Timon Gehr wrote:Continuing when an `in` contract fails needs to catch an `AssertError`. (This is already a problem.)What's the best solution to this? Changing the throwing of an AssertError to something like ContractViolatedException?
Aug 18
On Mon, Aug 17, 2026 at 06:57:04PM +0000, Quirin Schroll via Digitalmars-d wrote:TL;DR: D’s contracts are theoretically unsound, but there’s an easy fix.[...]Maybe C++ got something right, or rather, D got something wrong that C++ got right-er: *A virtual function’s postconditions don’t get limit what overriders do when invoked with arguments that don’t satisfy its preconditions.* The violation of this principle might be the reason why D’s contracts never took off.[...] IMO, the reason D's contracts never took off is because they are too complex to reason about when mixed with inheritance / polymorphism. I do use them in my own code, but rarely in class hierarchies, if at all. For simple domain checking of function inputs / verification of outputs, they can be pretty convenient for catching refactoring mistakes that break existing code. But mixing them with inheritance leads to mind-boggling complexities about who/what can/cannot override what/when/how, and what the resulting semantics are "supposed" to be -- it's just wayyy too much mental effort for marginal benefits. T -- Let X be the set not defined by this sentence...
Aug 17
On Monday, August 17, 2026 1:23:31 PM Mountain Daylight Time H. S. Teoh via Digitalmars-d wrote:On Mon, Aug 17, 2026 at 06:57:04PM +0000, Quirin Schroll via Digitalmars-d wrote:100%, though I don't think that contracts get used much in D ever without considering classes. As far as classes go, even assuming that D's implementation for them is correct, actually reasoning about them in the face of inheritance is simply too hard to be worth even considering in the vast majority of cases. Arguably, if you actually need that level of checks, them maybe you should rethink your design. And if you're just trying to catch bugs, well, at that point, you're getting into the issue of whether your checks are even correct. It's basically the same as writing proofs for code correctness. Past a certain point of complexity, they're not even vaguely worth it in the vast majority of cases, because you then not only have to debug and verify your program, but you have to debug and verify your proof. It's simply not worth it. Maybe it would make sense for someone like Boeing on NASA, but for anything approaching a normal program, it's really not. As for contracts in general (separate from classes and inheritance), D's current implementation makes them borderline pointless anyway. For them to be truly valuable, they need to be inserted at the call site based on how the caller is compiled rather than be compiled into the function itself and called based on how that function is compiled. As things stand, for in contracts, you might as well just put the assertions at the top of the function and not bother with the in contract. And out contracts are just pretty much useless in general regardless. Occasionally, they make sense, but in the vast majority of cases, what the result of the function should be depends on the input, and that doesn't work with out contracts (at least not with how D implements them). So, for the most part, unit tests are a far better way to verify what you might theoretically want to put in an out contract. The result of all of this is that contracts in D as currently implemented are pretty pointless. They do have some value with inheritance in the case where the conditions are simple enough to be reasonable (since you can't just put the assertions at the top of the function when inheritance is involved), but that's going to be rare for most folks. And classes are used pretty sparingly in the average D codebase anyway. And on top of that, you have the issue of whether assertions or exceptions are a better solution when it comes to validating function input. There are plenty of cases where assertions make good sense (and thus where in contracts could be useful if they were implemented properly), but with public-facing APIs, there's always the argument that defensive programming is be a better approach, because then it's impossible for bad input to make it past the checks at the top of the function instead of relying on the caller to have done their due dilligence (be it by compiling the code with contracts enabled or by simply verifying the conditions before calling the function). And of course, being able to make that decision properly requires that the programmer properly understand what assertions and exceptions are for and when it makes sense to use one or the other, and that seems to be bizarrely hard for some folks to grasp for some reason. All in all, I think that contracts are a nice idea, but D's implementation needs some work for them to make sense there. Even then though, I doubt that I would use them much. I'd use them more than I do now, but I doubt that I'd use them heavily, and I'd likely still never use them with classes, because I rarely use classes, and the logic for using contracts with them correctly is just too complicated. - Jonathan M DavisTL;DR: D’s contracts are theoretically unsound, but there’s an easy fix.[...]Maybe C++ got something right, or rather, D got something wrong that C++ got right-er: *A virtual function’s postconditions don’t get limit what overriders do when invoked with arguments that don’t satisfy its preconditions.* The violation of this principle might be the reason why D’s contracts never took off.[...] IMO, the reason D's contracts never took off is because they are too complex to reason about when mixed with inheritance / polymorphism. I do use them in my own code, but rarely in class hierarchies, if at all. For simple domain checking of function inputs / verification of outputs, they can be pretty convenient for catching refactoring mistakes that break existing code. But mixing them with inheritance leads to mind-boggling complexities about who/what can/cannot override what/when/how, and what the resulting semantics are "supposed" to be -- it's just wayyy too much mental effort for marginal benefits.
Aug 20









"Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> 