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
- Quirin Schroll (43/64) Sep 08 Yeah, I already thought if someone had already figured it out, it
- H. S. Teoh (13/23) Sep 08 [...]
- Quirin Schroll (12/34) Sep 16 If contract blocks just return `bool`, you can’t have an error
- Alexandru Ermicioi (7/9) Sep 16 In other words a tuple!
- Quirin Schroll (4/13) Sep 23 A string can already be null. What else than “no error” would a
- Alexandru Ermicioi (3/5) Sep 23 an error with no message.
- Alexandru Ermicioi (14/20) Sep 23 but really, in such cases, the expression itself can be set as
- Walter Bright (2/3) Sep 12 If anyone wants to fix D's contracts, feel free! It's worth doing.
- Richard (Rikki) Andrew Cattermole (5/9) Sep 13 And I tried to solve the biggest problem with contracts... the overuse
- Alexandru Ermicioi (14/27) Sep 13 Should be these 3:
- Richard (Rikki) Andrew Cattermole (6/15) Sep 13 @nogc you're on your own, no need to bake it into the language/runtime.
- H. S. Teoh (15/24) Aug 17 [...]
- Quirin Schroll (3/21) Sep 08 They might be hard to reason about because the way they work is
- 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 Monday, 17 August 2026 at 19:10:57 UTC, Timon Gehr wrote:On 8/17/26 20:57, Quirin Schroll wrote:Yeah, I already thought if someone had already figured it out, it would be you.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/This seems to be internal. I really have no idea how it’s done under the hood, but generally speaking, the language can bar the programmer from doing things it allows the compiler to do, simply because there’s a difference between an implementation detail in the compiler (which can be trusted to be correct) and user code. It is completely obvious what `assert` means when it’s lexically in a contract block, but a big complication are assertions that aren’t lexically in the contract block. They could be refactored parts of what logically belongs in many contract blocks (then catching the `AssertError` is fine), or it could verify something else entirely (making catching the `AssertError` conceptually wrong). There might be a case for deprecating contract blocks entirely in favor of contract statements, i.e. no more `in { doWhatEverIWant(); }`, only `in(Expression)` because the `Expression` must return a `bool` (or be convertible to one). Then, `Expression == false` is a contract violation, `Expression == true` is success. A thrown `Exception` should be “promoted” to an `AssertError`, and a thrown `Error` shouldn’t be touched. If a contract is more complex than a series of `in`/`out`/`invariant` expressions, a private function that returns a `bool` is probably more readable anyway; an immediately invoked lambda is also possible. Example: ```d void f(int[] xs, size_t i) in(xs[i] > 0); ``` The contract of `f` is violated when `i < xs.length` and `xs[i] <= 0`. If the first condition isn’t satisfied, `f`’s precondition shouldn’t even be checked, so if an overrider widens it, it would *still* give you the `RangeError` that essentially tells you `f`’s contract should be improved to `in(i < xs.length && xs[i] > 0)` or something equivalent. Logically speaking, even when the overrider widens the contract, the language should verify that `f.in == true` implies `f.out == true`, even if `f.out` is trivially `true` simply because `f.in` has more possible results than `true` and `false`, the others being something thrown. Essentially, there’s a big difference between violating a precondition (which might be fine in an inheritance scenario) and executing code that throws an `Error`.... 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.)
Sep 08
On Tue, Sep 08, 2026 at 10:23:37PM +0000, Quirin Schroll via Digitalmars-d
wrote:
[...]
There might be a case for deprecating contract blocks entirely in
favor of contract statements, i.e. no more `in { doWhatEverIWant();
}`, only `in(Expression)` because the `Expression` must return a
`bool` (or be convertible to one). Then, `Expression == false` is a
contract violation, `Expression == true` is success. A thrown
`Exception` should be “promoted” to an `AssertError`, and a thrown
`Error` shouldn’t be touched. If a contract is more complex than a
series of `in`/`out`/`invariant` expressions, a private function that
returns a `bool` is probably more readable anyway; an immediately
invoked lambda is also possible.
[...]
That, or deprecate contracts that assert, and have them return a bool
instead.
Having contracts return bool instead of asserting gives the compiler a
way to emit logic to handle things like covariant / contravariant
handling in class hierarchies that doesn't involve evil hacks around the
semantics of `assert` and catching AssertError's (and dabbling with
what's technically UB per language spec).
T
--
This is a tpyo.
Sep 08
On Tuesday, 8 September 2026 at 22:51:10 UTC, H. S. Teoh wrote:On Tue, Sep 08, 2026 at 10:23:37PM +0000, Quirin Schroll via Digitalmars-d wrote: [...]If contract blocks just return `bool`, you can’t have an error message anymore, which is really a loss. An improvement would be returning a `const(char)[]` for an error message, where `null` means no error. (It would also be easier to remember what means what, i.e. with returning a `bool`, there’ll be people who’ll mix up what `true` and `false` mean in that context.) However, I’d still prefer `in(isGood(input), "input bad")`.There might be a case for deprecating contract blocks entirely in favor of contract statements, i.e. no more `in { doWhatEverIWant(); }`, only `in(Expression)` because the `Expression` must return a `bool` (or be convertible to one). Then, `Expression == false` is a contract violation, `Expression == true` is success. A thrown `Exception` should be “promoted” to an `AssertError`, and a thrown `Error` shouldn’t be touched. If a contract is more complex than a series of `in`/`out`/`invariant` expressions, a private function that returns a `bool` is probably more readable anyway; an immediately invoked lambda is also possible.[...] That, or deprecate contracts that assert, and have them return a `bool` instead.Having contracts return `bool` instead of asserting gives the compiler a way to emit logic to handle things like covariant / contravariant handling in class hierarchies that doesn't involve evil hacks around the semantics of `assert` and catching `AssertError`'s (and dabbling with what's technically UB per language spec).The compiler can lower `assert`s that are lexically in the block to `bool`s. And it’s not UB in any shape or form if the compiler does it internally.
Sep 16
On Wednesday, 16 September 2026 at 10:28:08 UTC, Quirin Schroll wrote:... However, I’d still prefer `in(isGood(input), "input bad")`.In other words a tuple! With: 1. bool - the result of contract check 2. char, or interpolated string - the message in case of contract failing.
Sep 16
On Wednesday, 16 September 2026 at 20:00:57 UTC, Alexandru Ermicioi wrote:On Wednesday, 16 September 2026 at 10:28:08 UTC, Quirin Schroll wrote:A string can already be null. What else than “no error” would a null string signify?... However, I’d still prefer `in(isGood(input), "input bad")`.In other words a tuple! With: 1. bool - the result of contract check 2. char, or interpolated string - the message in case of contract failing.
Sep 23
On Wednesday, 23 September 2026 at 14:43:13 UTC, Quirin Schroll wrote:A string can already be null. What else than “no error” would a null string signify?an error with no message.
Sep 23
On Wednesday, 23 September 2026 at 20:40:48 UTC, Alexandru Ermicioi wrote:On Wednesday, 23 September 2026 at 14:43:13 UTC, Quirin Schroll wrote:but really, in such cases, the expression itself can be set as error message. For example index out of bound contract: ```d T get(sizet index) in (index < size) { ... } ``` in case of failure you can get: ``` Assertion `index < size` has failed. ``` obviuosly stack trace will show which method contract was for.A string can already be null. What else than “no error” would a null string signify?an error with no message.
Sep 23
On 8/17/2026 12:10 PM, Timon Gehr wrote:It's not like I didn't try: https://issues.dlang.org/bugs/7584/If anyone wants to fix D's contracts, feel free! It's worth doing.
Sep 12
On 13/09/2026 5:54 PM, Walter Bright wrote:On 8/17/2026 12:10 PM, Timon Gehr wrote:And I tried to solve the biggest problem with contracts... the overuse of assert. Language needs an Exception analogus to assert, but that got rejected. Until that is done, it will never have a chance to work correctly.It's not like I didn't try: https://issues.dlang.org/bugs/7584/If anyone wants to fix D's contracts, feel free! It's worth doing.
Sep 13
On Sunday, 13 September 2026 at 16:24:07 UTC, Richard (Rikki) Andrew Cattermole wrote:On 13/09/2026 5:54 PM, Walter Bright wrote:Should be these 3: IllegalArgumentException for in contract IllegalStateException for invariant IllegalResultException for out contract for nothrow it should throw Error variants since they are uncatcheable. for nogc plain assert and exit, or a global shared singleton immutable instance is thrown. But seriously, if a contract fails it's not like end of the world in most cases, so it should be handled by exceptions so app can decide whether it is possible to handle it or not, where default is ofc exit fast from app.On 8/17/2026 12:10 PM, Timon Gehr wrote:And I tried to solve the biggest problem with contracts... the overuse of assert. Language needs an Exception analogus to assert, but that got rejected. Until that is done, it will never have a chance to work correctly.It's not like I didn't try: https://issues.dlang.org/bugs/7584/If anyone wants to fix D's contracts, feel free! It's worth doing.
Sep 13
On 14/09/2026 6:32 AM, Alexandru Ermicioi wrote:Should be these 3: IllegalArgumentException for in contract IllegalStateException for invariant IllegalResultException for out contractThere is little point to doing that, due to function calls.for nothrow it should throw Error variants since they are uncatcheable. for nogc plain assert and exit, or a global shared singleton immutable instance is thrown.nogc you're on your own, no need to bake it into the language/runtime.But seriously, if a contract fails it's not like end of the world in most cases, so it should be handled by exceptions so app can decide whether it is possible to handle it or not, where default is ofc exit fast from app.Absolutely. Most asserts should not be asserts. And using asserts in production code like web servers are actually a form of DOS attack of your own creation.
Sep 13
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, 17 August 2026 at 19:23:31 UTC, H. S. Teoh wrote:On Mon, Aug 17, 2026 at 06:57:04PM +0000, Quirin Schroll via Digitalmars-d wrote:They might be hard to reason about because the way they work is unreasonable. (Sounds like a pun, but I mean it literally.)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.
Sep 08
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> 