Comment by 20k
4 days ago
This is the biggest problem with contracts, there's simply no way to actually say "please always execute this check". A lot of people replying seem to think that the critique is with the concept of contract based programming, but the C++ version of contracts is just weird
The programming model for C++ contracts as described by the authors, and spec, is that they are 'ghost code'. You must never assume that a contract check is actually executed, even if you've asked your compiler to execute your contract checks. This is because ABI/ODR problems can and will cause your contract checks to be deleted, as well as the 'observe' contract mode which is a strange one
So its hard to know what the use case for them is as a result. You can't use them for safety checks, because you must assume they won't be run. Hilariously, they actually introduce safety problems vs asset: Eg take two pieces of code:
assert(p != nullptr);
assert(p->some_value());
vs
contract_assert(p != nullptr);
contract_assert(p->some_value());
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 - while being perfectly well defined with asserts
I have a feeling that in 10 years the advice will be to never use contracts in anything approaching code that needs safety, as they're generally less safe than using an assert for no real reason. So what's their use case?
> You can't use them for safety checks, because you must assume they won't be run.
This is also true of assert, though. It can be compiled out so shouldn't be used for anything that must run.
Its pretty easy for me to guarantee that my asserts will all always be run via NDEBUG. I can't make that same guarantee for contracts
> Its pretty easy for me to guarantee that my asserts will all always be run via NDEBUG.
That's the opposite of what NDEBUG does. https://en.cppreference.com/cpp/error/assert
Critical bit:
> 1) If NDEBUG is defined as a macro name at the point in the source code where <cassert> or <assert.h> is included, the assertion is disabled: assert does nothing.
It also is impossible to guarantee it unless you're the only one compiling the program. If you've built a library that others can build and relied on asserts executing for your library to be correct, you wrote a bad library.
EDIT: Neither asserts nor these C++26 contract features should be used in a way that requires that they be executed. If you do, you're using them wrong because it is valid for someone to disable them as runtime checks. If the code must run, you should use some other mechanism, I'd recommend an if statement.
19 replies →
Interestingly, Java had exactly that in mind when introduced `assert` keyword in very early versions (2002). It's equivalent to an `if` that can throw a runtime AssertionException, but only if the program is launched with `-enableassertions` flag.
The idea is that you use `-enableassertions` only during development, and your program should never rely on it. But it helps IDEs and static analyzers to have a contract about code.
D has a keyword prefix `debug`. So, one can write things like:
So, you can throw the -debug switch on the compiler and the debug code is enabled. For asserts,
and the check is only run when the -debug switch is done. It works remarkably well.
Another interesting property of `debug` is it turns off the safety checks in the target statement that follows it. Because, sometimes you just gotta do some unsafe tricks to figure out where your code went wrong.
As does .NET, with Debug class and related annotations.
.NET had DbC support for a while, but it was a Visual Studio Enterprise product, based on rewriting MSIL, and eventually was killed, as I imagine very few people bothered using contracts when such a license was required.
JVM languages like Clojure, Scala and Kotlin have DbC constructs beyond only assert.