September 18, 2023
https://issues.dlang.org/show_bug.cgi?id=24149

          Issue ID: 24149
           Summary: Improve invariant checking with assert
           Product: D
           Version: D2
          Hardware: All
                OS: All
            Status: NEW
          Severity: enhancement
          Priority: P1
         Component: dmd
          Assignee: nobody@puremagic.com
          Reporter: qs.il.paperinik@gmail.com

One can explicitly run (“check”) invariants of aggregates using a reference
(pointer to struct/union type, object handle of a class) as the argument of an
`assert` statement. This is an bad feature: It looks like something that it
isn’t, namely (merely) checking if the reference is `null`. In almost all cases
one encounters `assert` (reading or writing), it asserts that a boolean
condition is true, and a reference is a boolean condition: `if (r)` is
equivalent to `if (r !is null)`. While opinions may differ if leaving `!is
null` implicit is good or bad style, it certainly is widely known and
understood. However, `assert` weirdly special-cases references and using `!is
null` is *required* if mere `null` checking is desired.

I suggest a new construct to check invariants: The `invariant` keyword after
`assert`, such that it is patently obvious what the intent is and what happens:
```d
Class c = …;
assert(c); // deprecated
assert(c !is null);  // Step 1
assert invariant(c); // Step 2

Struct s;
assert(&s); // deprecated
assert invariant(s);
```

For class handles, where the `assert` checking for `null` really means something, the fact that `assert` does two (quite different) things is clearly expressed and (by context) unneeded steps can be left out.

Of course, `assert invariant` can only be used with an aggregate value is
passed to it. It differs from `assert` in that structs/union objects are not
passed to it by pointer, but “normally”.
Also, it does not perform a `null` check, but requires that the reference is
not `null`.

Alternatively, `invariant(s)` can be made a primary expression that executes
the invariants and evaluates to `true`; then, uses of `assert` to check
invariants can state intent properly:
```d
Class c = …;
assert(c); // deprecated
assert(c && invariant(c)); // okay
assert(c !is null && invariant(c)); // good

Struct s = …;
assert(&s); // deprecated
assert(invariant(s)); // good
```

--