| ▲ | cogman10 7 hours ago |
| I don't like it. I'm all for signing up for a contract if it can turn a runtime error into a compile time error. However, this looks like it just makes more runtime errors. At that point, these seem no better than `assert` except for the fact that they promise to really assert (probably). The article also points out a big problem which is "Importantly, this aspect of Contracts is why contract assertions must not be used for error handling and input validation: If a function has in-contract requirements to report certain events as errors, that handling must be done with standard C++ control statements that are not optional, never with contract assertions." That sort of muddy definition of what contracts are supposed to be used for is almost certainly going to make a mess. It's not input validation, but parameter validation.. but not really validation but rather checks which error out, but it's not errors we are producing. |
|
| ▲ | 20k 3 hours ago | parent | next [-] |
| 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 assertsI 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? |
| |
| ▲ | Jtsummers 3 hours ago | parent | next [-] | | > 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. | | |
| ▲ | 20k an hour ago | parent [-] | | 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 | | |
| ▲ | Jtsummers 42 minutes ago | parent [-] | | > 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. | | |
| ▲ | 20k 5 minutes ago | parent [-] | | Of course? You simply undefine NDEBUG in your library Its trivially easy to guarantee that an assert always runs, and its well defined behaviour according to the spec. There's no reason not to if you want to use asserts in a way that guarantees that they always fire, which is a supported use case. Its common practice There's no equivalent for contracts |
|
|
| |
| ▲ | deepsun 3 hours ago | parent | prev [-] | | 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. | | |
| ▲ | WalterBright 2 hours ago | parent [-] | | D has a keyword prefix `debug`. So, one can write things like: ...
debug if (x) printf("x: %d\n", x);
...
So, you can throw the -debug switch on the compiler and the debug code is enabled. For asserts, debug assert(lookMa);
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. |
|
|
|
| ▲ | kccqzy 6 hours ago | parent | prev | next [-] |
| It’s a standard way to do pre and post conditions. Clojure pre and post conditions also behave this way. If you force these conditions into compile-time checks, well, then you are introducing a second type system with refinement types into the language. And type checking would take a long time. Imagine someone writes a precondition for a function where its integer argument x needs to satisfy collatz(x)==1; do you now expect the compiler to prove the collatz conjecture or verify it for all 64-bit integers every compilation? My experience using pre and post conditions in Clojure makes me really like them. They are a small but meaningful improvement over plain old assertions. And yes being able to turn them off is a feature not a bug. Don’t expect them to be like LiquidHaskell. |
| |
| ▲ | zone411 2 hours ago | parent | next [-] | | Off-topic, but collatz(x) == 1 for all positive 64-bit integers (actually, this has been verified up to 2^71: https://link.springer.com/article/10.1007/s11227-025-07337-0). I recently made this tool if anyone wants to try it with 1,000-digit integers or ranges: https://www.proofatlas.ai/explore/collatz/. | |
| ▲ | tgv 3 hours ago | parent | prev | next [-] | | SPARK (Ada) has compile time pre and post conditions, and it works well. If you declare the post-condition Collatz (n) = 1, the compiler can't help you, and compilation will fail. If you've got a difficult condition, you're going to have to help the theorem prover, often by introducing "ghost" functions, or by simply softening the condition. In the latter case, you can't prove total correctness, but you can often still prove things like absence of run-time errors. | |
| ▲ | kelseyfrog 6 hours ago | parent | prev [-] | | Just curious, but what's the definition of collatz(long) look like? | | |
| ▲ | Someone 6 hours ago | parent [-] | | https://en.wikipedia.org/wiki/Collatz_conjecture | | |
| ▲ | kelseyfrog 4 hours ago | parent [-] | | I meant the C++ definition of collatz that would verify the precondition. | | |
| ▲ | tyromaniac 4 hours ago | parent [-] | | I haven't tried it and am afk rn but I'd imagine you just define a constexpr function that implements collatz and use that in your contract. (Edit: if the input is constexpr itself)
Its also quire easy to think about specializing a few templates and doing it that way. If you look up an example of the fib sequence in c++ templates you'll get the general idea. | | |
| ▲ | kelseyfrog 3 hours ago | parent [-] | | I feel like I'd have something to learn from seeing the code. Won't you just run into a constexpr evaluation depth error with a suggestion to set -fconstexpr-depth? I don't see how this is the gotcha it's made out to be. | | |
| ▲ | tyromaniac 3 hours ago | parent [-] | | I'd exepct something like that to happen. Worth reiterating that the whole scenario is for a hypothetical different implementation of contract that would execute comptime, as noted in the parent thread. If you wanted a c++26 preconditon for collatz tho, here it is.
```cpp
bool collatz(unsigned long long n) {
while (n != 1) n = n % 2 ? 3 * n + 1 : n / 2;
return true;
} void f(unsigned long long n) pre(collatz(n));
```
That's basically what you'd write if you were doing it with asserts, as the linked article states, just now you add it to the declaration. |
|
|
|
|
|
|
|
| ▲ | pandaman an hour ago | parent | prev | next [-] |
| >I'm all for signing up for a contract if it can turn a runtime error into a compile time error. You can get compile-time errors with static code analysis tools with it but the main goal is to direct the compiler towards optimizations for restricted case, IMHO. If you know beforehand the kind of values you are using then the only sure way to let the compiler know is to write code in such a way that the values you don't expect result in UB, which is not always possible or convenient. Contracts allow a regular way to do the same. It does not mean the compiler will use this knowledge but it will remain in your code and might be used eventually. For example, if you want to align an integral value on x you can write something like ((value + x-1)/x)*x, however, if x is a power of 2 then you can do (value + x-1) & ~x, which is likely faster. As of now, gcc 16 does not do this optimization even with contracts, but if you recorded this in code, some future compiler might do this. Without contracts the chances for this are much lower. |
|
| ▲ | OneDeuxTriSeiGo 7 hours ago | parent | prev | next [-] |
| contracts are intended to document hoare logic. i.e. pre, post, invariant. They can be compile time if the compiler can verify them statically (where they become a noop), runtime otherwise, or disabled entirely if you can't prove them statically but don't want the overhead cost. Realistically contracts will gradually evolve towards static/compile time asserts as tooling improves. And like the blog post states, contracts are explicitly not input validation. They are there to encode what you expect and what you don't internally in your code rather than just documenting those pre/post/invariant conditions in comments. |
| |
| ▲ | cogman10 7 hours ago | parent | next [-] | | > Contracts are explicitly not input validation. They are there to encode what you expect The line between what's "input validation" and what's "What you expect" is very muddy. Input validation is a statement of "this is input I expect". Is it input validation to say a string must be at least some length characters? What if that string is a password? What if it's an MD5 sum? It SHOULDN'T be used for input validation but it will be. And almost certainly the person that does use it for input validation will argue that they were just laying out expectations. | | |
| ▲ | spider-mario 3 hours ago | parent | next [-] | | > The line between what's "input validation" and what's "What you expect" is very muddy. Input validation is a statement of "this is input I expect". > Is it input validation to say a string must be at least some length characters? What if that string is a password? What if it's an MD5 sum? Think of it this way: pre-conditions are what the function assumes to be true when it’s called. If it would not be justified to make that assumption at the point where the function is called, it is the responsibility of the caller to perform a runtime check to ensure that it applies. So you might well have a precondition at the level of the function and an explicit check of the exact same condition on the caller side to ensure that it’s respected. If the caller fails to ensure that the condition is fulfilled, and the precondition thereby trips, that’s a programming error. | | |
| ▲ | OneDeuxTriSeiGo 7 minutes ago | parent [-] | | Yep this is exactly it. It's a sanity check that enforces the invariants so that when someone else violates those invariants and they don't know what went wrong they can easily debug it. |
| |
| ▲ | OneDeuxTriSeiGo 6 hours ago | parent | prev [-] | | > Is it input validation to say a string must be at least some length characters Yes. Yes it is. Input validation should happen at the public API and then contracts after that. Contracts are very explicitly for things you don't want to ever see happen. They are "something went wrong". So your API would accept a function that handles input validation and maybe has its own contract for things it explicitly doesn't handle but then internally you use contracts to enforce that the string is already input validated and you don't have to enforce edge cases. |
| |
| ▲ | nickorlow 6 hours ago | parent | prev | next [-] | | > They can be compile time if the compiler can verify them statically (where they become a noop) This only happens during constant evaluation. For example, the following could be determined statically, but compiles w/o error on gcc int add_nums(int x, int y) pre(x >= 0)
pre(y >= 0)
{ return x + y;
}int result_rt = add_nums(-1,-1); // no compile error | | |
| ▲ | bluGill 6 hours ago | parent [-] | | It compilers on the current gcc version (or at least the one you tested). Future gcc can make it an error. | | |
| ▲ | nickorlow 6 hours ago | parent | next [-] | | Yes, though that wouldn't be consistent w/ the standard | | |
| ▲ | bluGill 6 hours ago | parent [-] | | The standard doesn't require compilers find all such cases. The standard allows it, but doesn't require it. There are many known cases where the compiler cannot find a case (at least not in a reasonable amount of time) | | |
| ▲ | nickorlow 5 hours ago | parent [-] | | The consequence of not being required in the standard is that compilers may not implement it and if they do, they will likely have different implementations/functionality |
|
| |
| ▲ | cyberax 4 hours ago | parent | prev [-] | | That's like compiling with -Wall. Stuff will keep breaking randomly, so people will just disable it. | | |
| ▲ | bluGill 2 hours ago | parent [-] | | Some people. Most projects I know of build -Wall and fix all the errors. Developers all run - Error. 30 years ago this was madness but compilers are good about fixing false positives and so it is reasonable. It isn't reasonable to expect downstream (i.e. Debian) to fix warnings but developers generally do for code they maintain. |
|
|
| |
| ▲ | 7 hours ago | parent | prev | next [-] | | [deleted] | |
| ▲ | 7 hours ago | parent | prev | next [-] | | [deleted] | |
| ▲ | rramadass 2 hours ago | parent | prev [-] | | https://news.ycombinator.com/item?id=50108112 |
|
|
| ▲ | dataflow 7 hours ago | parent | prev | next [-] |
| The bit about contracts not being input valuation is no different than the case for assertions. It's basically saying that if assertions were inappropriate, so are contracts. So if you find that like blurry, the blurriness predates contracts. That said, the distinction is generally pretty clear: an assertion failure (and therefore a contract failure) indicates a bug. It must never happen, and therefore it must always be OK to suppress the validation. If you see it fire, that's a bug report, and your code needs fixing. And if your contract to the caller is that you will enforce some constraint on them (as opposed to assuming they already took care of ensuring it), then you need the validation in your runtime code. |
|
| ▲ | wongarsu 6 hours ago | parent | prev | next [-] |
| I would phrase the difference between contracts and input validation like this: - If it can happen, but shouldn't, that's input validation (e.g. the user inputs an empty name) - If it can't happen and shouldn't, that's a contract (e.g. I checked that this string is non-empty in the function that calls this one, so I already know it's non-empty. Also my linked list has a head and a tail because that's how my constructor made it) Most bugs happen in the space of things that the programmer thinks shouldn't happen but happen anyways. That is why contracts are useful. But in a bug-free program they also don't do anything, so allowing you to turn them off with a compiler flag is reasonable. Just like asserts, but a bit more useful for tooling, IDEs, etc. Also with an observe mode that allows you to find bugs in production without crashing your whole process |
|
| ▲ | cenamus 7 hours ago | parent | prev | next [-] |
| They are basically asserts that you can put in headers. Which is great because asserts are usually useless to IDEs because you only get the headers for libraries. And with the 'observe' mode you can turn them on in production code right away and collect data without a failed assert terminating. |
|
| ▲ | slaymaker1907 2 hours ago | parent | prev | next [-] |
| You should check out the Racket programming language. They actually don’t have types at all in their documentation, they only have preconditions and postconditions which are very helpful in telling you if a function expects non-negative integers or something. |
|
| ▲ | rurban 4 hours ago | parent | prev | next [-] |
| In my C compiler rcc I throw compile-time errors of course. Not a full SMT solver like z3, just a self written solver, but it works fine and fast enough. Runtime errors are just glorified asserts, lame. |
|
| ▲ | germandiago 4 hours ago | parent | prev | next [-] |
| Preconditions cannot be checked at compile time in all cases. |
|
| ▲ | gpderetta 7 hours ago | parent | prev | next [-] |
| they are literally meant to be better asserts, that can be shown on a function declaration. |
| |
| ▲ | cogman10 7 hours ago | parent [-] | | Well, except for the fact that asserts can be used during debugging to do input validation while these shouldn't be used for that. They also introduced `contract_assert` which seems to have no benefit over assert other than it's more likely to not be disabled. | | |
| ▲ | wongarsu 6 hours ago | parent | next [-] | | Asserts also shouldn't be used for input validation. If during debugging you are peppering asserts that restate things that per the API contract should already be true, but you want to make extra sure your bug isn't caused by something slipping through the cracks: that's what what contracts are for. | |
| ▲ | OneDeuxTriSeiGo 7 hours ago | parent | prev [-] | | nah contract_assert has entirely different semantics than normal asserts. It includes checkpoint semantics (something that is part of contracts in general) as well as processing through the contract violation handlers. Functionally contract_assert should eliminate the need to use assert at all and is an over all substantial UX improvement. |
|
|
|
| ▲ | rramadass 3 hours ago | parent | prev | next [-] |
| You need to study Bertrand Meyer's "Design by Contract" and "Applying Design by Contract" papers - https://news.ycombinator.com/item?id=48358691 There are also ways to turn runtime contract checks into static verification checks. See my other comment for details/links - https://news.ycombinator.com/item?id=50108112 |
|
| ▲ | FuriouslyAdrift 5 hours ago | parent | prev [-] |
| If someone wants contracts in C++, why not just use Eiffel? |
| |
| ▲ | gpderetta 4 hours ago | parent | next [-] | | Because then they wouldn't have contracts in C++. | |
| ▲ | binary132 5 hours ago | parent | prev [-] | | If someone wants contracts in C++, now they can just keep using C++. Which is good, because a lot of people and projects are stuck with C++, so the actual question is merely: can you have contracts in your project or can’t you? |
|