Maybe D was wrong on contracts
Quirin Schroll
qs.il.paperinik at gmail.com
Tue Sep 8 22:23:37 UTC 2026
On Monday, 17 August 2026 at 19:10:57 UTC, Timon Gehr wrote:
> 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/
Yeah, I already thought if someone had already figured it out, it
would be you.
>> ...
>> 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.)
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`.
More information about the Digitalmars-d
mailing list