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.
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.
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
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.
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.
> 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.
> 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.
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)
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
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.
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?
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.
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.
If you enjoy this style of programming, I wrote a bit on why contracts and capabilities (ie "allocates memory", "performs i/o") are going to become essential for LLM generated code:
Rust is quite complicated but people like it a lot. Python too in a way - it's quite large and complicated but it exposes the complexity in a very gradual way and you can ignore so much of the language and still get things done.
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.
Languages like Go, is how you end up with what appears simple on the surface, while in reality is full of gotchas and boilerplate for what complex languages support out of the box.
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,....
While this behavior can be counterintuitive, it's easy to understand once you have the right mental model of interface values. A nil interface is an empty interface. Once p is assigned to i, then i is no longer empty: it holds a nil pointer to an int. Whether the interface is empty or not is one question, answered by comparing it against nil. Whether the value the interface holds is a nil value is another question, answered by downcasting and then checking the resulting value.
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.
On the one hand, almost everyone (including C++ fans) agrees that the language is too big. On the other hand, comtracts just formalize an existing and useful concept.
I view C++ (and increasingly C# and Java) as a test bed for language features. You throw every possible feature into it and people can then see what makes sense and what doesn't. That allows others to pick features and use them for more opinionated and simpler languages.
Lots of programmers match the right language to the right job, I think the zen of C++ is embracing the ugly as a feature; there are so many subsets of that language you never have to leave it.
Exactly, you can code like C devs (Petzold used the C subset in his famous Win32 book), go to the extreme of GUI, ORMs, Web frameworks in 1990's, inspired from Smalltalk before Java and .NET took over that role, go crazy with Haskell style programming, or chose somewhere in between.
The article makes no mention of optimisation use cases. In case anyone already knows off the top of their heads and before I start digging for the answer, is part of the intent here for these to inform the optimiser stages, perhaps similarly to what the if (!(some_condition)) { __builtin_unreachable(); } idiom can achieve?
Allegedly the pre and post conditions, in varying degrees of certainty according to the evaluation semantics selected, are propagated to the optimization passes of gcc and llvm. However, I haven’t seen any detailed blog posts or summaries of the effectiveness or reliability of this feature. Sorry it’s not a lot of info, but it was what I knew off hand based on previous reading.
...yup, looks like there's no way to get the optimisation benefit in "ignore" mode, which is the only mode that doesn't explicitly compile the runtime checks: https://godbolt.org/z/KTx1Pc3aa compare codegen for test3 (the pattern that would be useful) vs test4 (the best we actually have)
I'm looking forward to seeing where these go in the future. Can solvers prove my code correct (at least in some trivial cases)? What should happen in a unit test (sometimes I want to test that my program handles out of range values, sometimes it is expensive top check and so I put the contract in to avoid the check at runtime...) Can/should the optimizer account for contracts (same question as unit tests).
See the papers "Soft Contract Verification" and "Soft Contract Verification for Higher-Order Stateful Programs" (Nguyễn et al.) In general, there's a lot of academic work on the topic coming from the Racket community.
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.
I haven't seen those papers. The general thought seems to be that progress is good enough that a restricted subset of C++ can be proved (which is getting less restricted all the time, though probably never will be everything) and the solvers will be able to detect where they give up: if you want a solver you will have to write the code they can prove and that is an acceptable limitation.
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.
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.
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.
I'm expecting to see talks (and buy books, read blogs...) in 3 years by someone who spent a lot of work adding contracts to a small but significant code base. This is the type of question they will answer.
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.
Hmm, I thought I had watched all his talks on contracts by now, but I must have missed a few (which isn't a surprise - there are a lot of conferences out there that I can't watch them all and get anything done. I end up ignoring a lot of them)
I am very bullish on constraints based programming like this.
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.
Take a look at Racket (or Rhombus if allergic to parens). Support for contracts there is stellar and provides everything you mentioned. You can use `define/contract` for inline contracts, or you can attach contracts when exporting names from a module using `contract-out` declaration. Contracts are first-class, can be defined separately and attached wherever you want (importantly, this includes values (structs, classes) as well as functions). There are higher-order contracts that generate new contracts from existing ones. There's support for both static and dynamic metadata for almost all kinds of entities in code (ie. both the syntax and the semantic entity it produces can be annotated with anything). I wouldn't recommend using Racket/Rhombus in production, but it's a great preview of what might become commonplace in a decade or two :)
I dislike this. But honestly, I dislike assert statements. I mainly write code that cannot fail. And if something does go wrong, I must handle that in an appropriate way. Asserts are never the way because they terminate the program.
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.
I can see the appeal of having 'code as documentation' in header files, but personally I find any type of runtime asserts to be too heavy handed. Graceful error handling, in my opinion, is always preferable. This type of functionality is what is continuing to drive me away from C++. If your preconditions and postconditions matter, document them in a comment block.
Graceful handling is a trade off. Sometimes the check is expensive, if everybody normally does it right you don't want to check (particularly in a tight loop), but you don't want to lose this. I work in embedded, in production our CPUs are not powerful enough to risk all those runtime checks, but I want to turn them on when I work on my workstation - in hopes that if there is a problem we catch it.
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.
You can set a "contract evaluation semantic" for contracts. One of them is "observe", which only prints a message on contract violations. With "ignore", nothing at all happens.
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.
I have seen far too many times when the comment in the implementaiton doesn't match reality to accept pre/post conditions in a comment. I want my contracts someplace where they get evaluated from time to time so that they are fixed when they are wrong.
An anthropic employee did a talk earlier this year or last and i think it was said that yeah, they eat it up. Tools to find errors because it is visible is great
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.
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.
Just curious, but what's the definition of collatz(long) look like?
https://en.wikipedia.org/wiki/Collatz_conjecture
I meant the C++ definition of collatz that would verify the precondition.
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
Preconditions cannot be checked at compile time in all cases.
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.
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.
> 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.
> 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.
> 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)
{ }int result_rt = add_nums(-1,-1); // no compile error
It compilers on the current gcc version (or at least the one you tested). Future gcc can make it an error.
Yes, though that wouldn't be consistent w/ the standard
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)
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
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.
If someone wants contracts in C++, why not just use Eiffel?
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?
they are literally meant to be better asserts, that can be shown on a function declaration.
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.
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.
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.
This reminds me the "Design by Contract" concept from Eiffel programming language:
https://en.wikipedia.org/wiki/Design_by_contract
https://www.eiffel.com/values/design-by-contract/introductio...
If you enjoy this style of programming, I wrote a bit on why contracts and capabilities (ie "allocates memory", "performs i/o") are going to become essential for LLM generated code:
https://gavinray97.github.io/blog/design-by-contract-and-eff...
An alternative is to write data abstractions (encapsulation, abstract data types, etc. LLM really avoid those).
First thing that popped into my mind as well I read the entire book on this and I really enjoy the philosophy
Random tidbit: I wrote a GCC compiler plugin years ago to add Contracts to C/C++ while waiting for this to make it into the standard
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
Maybe just me, but huge complicated languages like C++ seem as unappealing as ever.
If I’m going to write code by hand, the requirement for a beautiful pleasant language is bigger than ever.
Rust is quite complicated but people like it a lot. Python too in a way - it's quite large and complicated but it exposes the complexity in a very gradual way and you can ignore so much of the language and still get things done.
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.
Languages like Go, is how you end up with what appears simple on the surface, while in reality is full of gotchas and boilerplate for what complex languages support out of the box.
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,....
Written a few hundred kloc of go and I'd never heard of double nil. Turns out it's nasty.
While this behavior can be counterintuitive, it's easy to understand once you have the right mental model of interface values. A nil interface is an empty interface. Once p is assigned to i, then i is no longer empty: it holds a nil pointer to an int. Whether the interface is empty or not is one question, answered by comparing it against nil. Whether the value the interface holds is a nil value is another question, answered by downcasting and then checking the resulting value.
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.
> url based imports that requires source code and rewrites when the host changes
This is not the case. It can be avoided quite easily with 'replace' directives and/or GOPROXY.
On the one hand, almost everyone (including C++ fans) agrees that the language is too big. On the other hand, comtracts just formalize an existing and useful concept.
Profiles should (hopefully) rein this in by getting people to start subsetting language features in their codebases
Let's see, they are still a bit of a dream regarding what they wish for, and what current static analysers can achieve, especially the lifetime one.
Exactly. Many years ago people used to use C `struct` for objects but then that was formalized as `class`.
C++ "class" is the exact same user-defined type as C++ "struct" but with different defaults for privacy.
I view C++ (and increasingly C# and Java) as a test bed for language features. You throw every possible feature into it and people can then see what makes sense and what doesn't. That allows others to pick features and use them for more opinionated and simpler languages.
Lots of programmers match the right language to the right job, I think the zen of C++ is embracing the ugly as a feature; there are so many subsets of that language you never have to leave it.
Exactly, you can code like C devs (Petzold used the C subset in his famous Win32 book), go to the extreme of GUI, ORMs, Web frameworks in 1990's, inspired from Smalltalk before Java and .NET took over that role, go crazy with Haskell style programming, or chose somewhere in between.
The article makes no mention of optimisation use cases. In case anyone already knows off the top of their heads and before I start digging for the answer, is part of the intent here for these to inform the optimiser stages, perhaps similarly to what the if (!(some_condition)) { __builtin_unreachable(); } idiom can achieve?
I don't get the impression that these are meant to influence optimization at all.
Allegedly the pre and post conditions, in varying degrees of certainty according to the evaluation semantics selected, are propagated to the optimization passes of gcc and llvm. However, I haven’t seen any detailed blog posts or summaries of the effectiveness or reliability of this feature. Sorry it’s not a lot of info, but it was what I knew off hand based on previous reading.
But for the most use you'd specifcially want them used for optimizations when they are NOT being evaluated as that's the build where perf matters.
...yup, looks like there's no way to get the optimisation benefit in "ignore" mode, which is the only mode that doesn't explicitly compile the runtime checks: https://godbolt.org/z/KTx1Pc3aa compare codegen for test3 (the pattern that would be useful) vs test4 (the best we actually have)
Will compilers be able to optimize functions based the assumtions that contracts hold (even if they aren't being checked)?
I'm looking forward to seeing where these go in the future. Can solvers prove my code correct (at least in some trivial cases)? What should happen in a unit test (sometimes I want to test that my program handles out of range values, sometimes it is expensive top check and so I put the contract in to avoid the check at runtime...) Can/should the optimizer account for contracts (same question as unit tests).
See the papers "Soft Contract Verification" and "Soft Contract Verification for Higher-Order Stateful Programs" (Nguyễn et al.) In general, there's a lot of academic work on the topic coming from the Racket community.
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.
I haven't seen those papers. The general thought seems to be that progress is good enough that a restricted subset of C++ can be proved (which is getting less restricted all the time, though probably never will be everything) and the solvers will be able to detect where they give up: if you want a solver you will have to write the code they can prove and that is an acceptable limitation.
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.
Where do I put these?
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.
Strictly speaking you put them in the header.
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.
I'm expecting to see talks (and buy books, read blogs...) in 3 years by someone who spent a lot of work adding contracts to a small but significant code base. This is the type of question they will answer.
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.
C++ contracts design is based on an internal Bloomberg framework, see John Lakos recent talks on contracts.
Hmm, I thought I had watched all his talks on contracts by now, but I must have missed a few (which isn't a surprise - there are a lot of conferences out there that I can't watch them all and get anything done. I end up ignoring a lot of them)
Since this is for C++26, you use modules.
I am very bullish on constraints based programming like this.
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.
Take a look at Racket (or Rhombus if allergic to parens). Support for contracts there is stellar and provides everything you mentioned. You can use `define/contract` for inline contracts, or you can attach contracts when exporting names from a module using `contract-out` declaration. Contracts are first-class, can be defined separately and attached wherever you want (importantly, this includes values (structs, classes) as well as functions). There are higher-order contracts that generate new contracts from existing ones. There's support for both static and dynamic metadata for almost all kinds of entities in code (ie. both the syntax and the semantic entity it produces can be annotated with anything). I wouldn't recommend using Racket/Rhombus in production, but it's a great preview of what might become commonplace in a decade or two :)
I dislike this. But honestly, I dislike assert statements. I mainly write code that cannot fail. And if something does go wrong, I must handle that in an appropriate way. Asserts are never the way because they terminate the program.
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.
I can see the appeal of having 'code as documentation' in header files, but personally I find any type of runtime asserts to be too heavy handed. Graceful error handling, in my opinion, is always preferable. This type of functionality is what is continuing to drive me away from C++. If your preconditions and postconditions matter, document them in a comment block.
Graceful handling is a trade off. Sometimes the check is expensive, if everybody normally does it right you don't want to check (particularly in a tight loop), but you don't want to lose this. I work in embedded, in production our CPUs are not powerful enough to risk all those runtime checks, but I want to turn them on when I work on my workstation - in hopes that if there is a problem we catch it.
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.
You can set a "contract evaluation semantic" for contracts. One of them is "observe", which only prints a message on contract violations. With "ignore", nothing at all happens.
As they used to say on slashdot:
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.
I have seen far too many times when the comment in the implementaiton doesn't match reality to accept pre/post conditions in a comment. I want my contracts someplace where they get evaluated from time to time so that they are fixed when they are wrong.
No worries, this will probably land in C as well, static_assert was already.
id' normally hate this but with agents this is awesome
An anthropic employee did a talk earlier this year or last and i think it was said that yeah, they eat it up. Tools to find errors because it is visible is great