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.
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?
This is also true of assert, though. It can be compiled out so shouldn't be used for anything that must run.
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.
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
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.
...
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.
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.
(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.
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.
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.
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.
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.
> 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.
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.
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
It isn't reasonable to expect downstream (i.e. Debian) to fix warnings but developers generally do for code they maintain.
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.
- 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
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.
And with the 'observe' mode you can turn them on in production code right away and collect data without a failed assert terminating.
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.
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.
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
https://www.digitalmars.com/ctg/contract.html
I implemented it in D as well. But, it just turned out to not be very useful.
https://en.wikipedia.org/wiki/Design_by_contract
https://www.eiffel.com/values/design-by-contract/introductio...
https://gavinray97.github.io/blog/design-by-contract-and-eff...
Instead, my custom asset function (in C) looks like this:
> void AssertWF(bool assertResult, char *format, ...)
Example: > AssertF(index < container.size(), "index:%d < container.size():%d", index, container.size());
If the first parameter is false, a detailed error message is printed: > fputs("Assert failed: ", stderr);
> vfwprintf(stderr, format, ...);
> abort();https://gavinray97.github.io/blog/adding-invariant-to-cpp-de...
The post turned out to be more useful for an intro to writing GCC plugins than anything, but at least I had fun
If I’m going to write code by hand, the requirement for a beautiful pleasant language is bigger than ever.
I agree with you about simple languages though. I really like Go - the "there should be only one way to do things" from the Zen of Python actually applies to Go in a way that it doesn't apply to Python at all.
That is how you get double nils, exception handling that pretends not to be one, url based imports that requires source code and rewrites when the host changes, generators by code patterns, generics that miss some language constructs,....
var i interface{} // (Type: nil, Value: nil) -> i == nil is TRUE
var p *int = nil // p is nil
i = p // (Type: *int, Value: nil) -> i == nil is FALSE!I don't blame people for being confused by this the first time they encounter it. However, anyone who finds it persistently challenging to understand is not going to have much luck with the subtler language features of C++, Rust, Typescript, etc.
This is not the case. It can be avoided quite easily with 'replace' directives and/or GOPROXY.
Doesn’t hurt that it has fantastic, minimal, navigable documentation.
You can see the entirety of the language on a single page: https://cppreference.com/
In short: yes, some classes of contracts can be statically proven. Not all of them, and it depends on the language's semantics, so it's probably not easy to retrofit into C++. But there's work being done on unifying types and contracts, moving back and forth between static proof and dynamic enforcement, and it's all pretty interesting. Proving contracts statically eliminates runtime overhead; generating contracts from types makes it possible for a gradually typed language to be sound.
Contracts also allow a solver to say "I can't prove this messy function, but I can at least prove everything around it". Which is to say if we prove the precondition is met, and if we assume the post conditions are met, we can prove everything else despite the messy parts we can't do. This at least lets us know where the messy code is.
More links in my previous comment chain here - https://news.ycombinator.com/item?id=48544022
The basic idea is that you generate "Verification Conditions"(VCs) by mapping the function onto a Hoare Triple viz. {Pre} Function-Body {Post} and then applying Dijkstra's WP-Calculus mechanically to step backward from the Post to the Pre thus generating a mathematical formula.
The programmer provided contracts are merged into this formula to generate the complete VC which is a logical implication viz; Pre ==> WP(contracts-from-function-body, Post).
This is then handed off to a automated solver which verifies that Post is met when Pre is True in all the paths.
See also, Combining Tests and Proofs with Contracts for Better Software Verification - https://news.ycombinator.com/item?id=50108206
If I put them in the header, I have to include half the world because I can't forward declare `Foo foo` argument if I want to check `foo.size() > 3` or similar in a pre/post-condition. This is a non-starter, keeping headers lean is the only way to not end up with 1 hour compiles.
If I put them in the cpp, they lose their only advantage over asserts - that they also double as documentation in the headers.
Did we add 50 pages to the standard for a slightly fancier assert? I feel like this could've been one builtin and a footnote:
contract_assert(expr) - if expr evaluates to false, the behavior of the program is undefined. Implementations are encouraged to emit a runtime check for this in debug builds, or a compiler error in cases where they can statically determine, that the assert will fail.
I think the right answer is in the header, and in turn that means someone needs to answer your objection - how do you avoid long compiler times? I suspect modules is a large part of the answer, which means the guide/talk will also be how to make modules work in the real world. Remember though, that is all guesses I wouldn't be surprised if I'm wrong.
"Our Journey Toward a Fully Backward-Compatible, UB-Safe, ISO C++"
The first declaration of a function must include the contract pre/post conditions and follow-up re-declarations of the function must either not include any contract conditions or the same conditions as the initial declaration.
And you can avoid re-including the world because pre/post conditions should be externally visible behavior or call out to functions for pre/post checks on internal behavior.
Essentially you either define them using what you have in scope during the initial declaration or you call out to a function located elsewhere that can have access to more/internal scope and do more complicated checks.
For formal verification, the design is way off.
The main problem is code organization and syntax. Tacking stuff like this onto fuction declarations is too basic.
Ideally, I think we need better constructs, e.g. dettached constraints like to the function declaration. You can define constraints separately, in the same namespace or w/e, add notes, comments, etc. and the function declaration can be used with intellisense to jump to the constraint. You could even tag the defintions.
In modern AI-based workflows, the agent can pull up an explanation on the function, and point directly to the constraints, with strong confidence esp. if it's got explicit metadata.
All jokes aside, I genuinely think that non-algol syntax is a non-starter. We're too far along now, that it's easier for these features to come to java/rust/typescript/cpp than it is to switch completely to academic langs that have terrible tooling support.
Contacts are also a guaranteed syntax. Runtime checks provide no way for tools to know if the condition is expected to never happen - this is a bug; vs this is a normal user error we need to check for. A contract means theorem provers can find code paths where a bug could happen - of course we don't have theorem provers for C++, but this is seen as a requirement to get them.
From the "C++ design committee was so preoccupied with whether they could, they never stopped to think whether they should" department.
Asserts:
1. You don't like the being conditional on NDEBUG ot aborting the program - roll out your own. Will still be a one-liner.
2. You want them act as documentation of pre-conditions in headers - just add a comment there.
The only somewhat reasonable development here are the post conditions for functions with multiple returns, but post/s are dramatically less common than pre/s, so their utility is also debatable.
In strictly my opinion, I don't have a problem with pre/post condition comments in a header. They clearly describe important details. Introducing yet another way to support asserts seems very unnecessary to me, but I guess I'm not obligated to use it either.