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.
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.
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.
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 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.
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.
This is why in my own code I define macros assume and require. The former documents assumptions, asserts for debug builds, and calls __builtin_unreachable for production builds. The latter documents validated requirements, always runs, and is guaranteed to produce a core dump if violated.
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
The consequence of being required is the standard is limited to exactly what we are sure everybody can do today. If there are advances in proving C++ code the new advances cannot be used to tell you about more bugs.
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.
For my own code I generally -Wall -Werror and if that breaks a prospective dependency in a nontrivial manner I find a replacement. I'll also tend to go looking for a replacement if a prospective dependency breaks when compiled with either asan or ubsan. Why would I voluntarily subject myself to such shoddy code?
I perform my own builds with -Wall -Werror. But it is a kindness to users of your code to provide a build flag that excludes -Werror. This allows user who are building with a GCC compiler version later than mine to sill build the code without having to modify the build scripts.
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.
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 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.
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.
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.
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.
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
FTFY: "Most bugs happen in the space of things that the programmer thinks can't happen but happen anyways". I'm sure we all have stories about some baffling error that simply couldn't have occurred, it's impossible, and then you spent an hour staring at the code, reading the tracing logs, and then finally finding a single-character typo responsible for it. Whoops, it's not supposed to be "if (p != nil) { return false; }", it's meant to be "if (p == nil) { return false; }"!
But yeah, that's precisely what contracts are (at least originally) intended to be, but you can quite clearly see from this comment section that this is not how they're going to be used. Well, this surely gonna be "fun" experience down the line for the C++ programmers...
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?
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.
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:
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?
> 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.
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
No, it's not. Don't do that. Someone (like me) will replace assert.h wholesale and it will not behave the way you expected. Always define your own internal macro and always provide facilities for the user to override it.
It's literally standard practice. You import assert.h unless the end user has defined mylib_assert in which case you don't import and use that instead. Failing to do this can result in all kinds of dumb problems thanks to the namespace being global.
This is totally backward. It's literally not any standard practice I have ever seen. In fact, it is an anti-practice. If you go around re-#defining standard library macros then you get what you deserve, and get no sympathy from me.
And it seems completely sensible that if I'm writing a C++ library that it's header files should include assert.h, and use assert()'s in its inline functions. And you will get no sympathy from me if you break my code by redefining the library behavior of assert().
I don't see why. The user invoking the compiler should be the only one controlling NDEBUG (or its equivalent). So far the only bad idea I've run into is the occasional library author blindly importing assert.h which happily stomps all over my custom implementation.
There's a whole host of things that libraries need to be extremely conservative about touching. Assert, threads, signals, etc.
That's not what the C/C++ spec says people can rely on with assert/NDEBUG. Like don't get me wrong, you can do what you want, but you can and will break code in ways that introduce memory unsafety with this approach
You sound as if "my asserts will all always be run via NDEBUG" makes perfect sense and not the exact opposite of what you've expanded it to
> Its common practice
not really, no
post ++11 cpp has been moving away from preprocessor (pedagogically and culturally), so "let's force-undefine NDEBUG" trick isn't at the top of most people's minds
You use the status of NDEBUG to control whether or not your asserts are run. I don't understand how this is complex
If you want to use asserts, you need to control the definition of NDEBUG. You either do this with your build system (common), or manually (slightly less common). This is literally <assert.h> 101
The preprocessor is widely used for a variety of things when its necessary, including #includes and #defines, because there's literally no other mechanism for this. Its bizarre to say that nobody controls the definition status of NDEBUG because people don't use the preprocessor
I don't know if it's good practice but I replace assert and assert.h with my own code (you just have to make sure to make the necessary platform specific calls in your replacement headers). So I always know exactly how assert will behave in my own codebase even when invoked by a library.
It's worth noting that the motivation for the above was poorly written libraries that hardcode the use of the builtin assert. So I agree with you completely on that point.
That said I've never been particularly impressed with how assert is defined by the standard and now somehow contracts appear set to be even worse.
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:
...
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.
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.
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.
>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.
Can you go into why you didn't find them to be useful? Seems if done right they should allow many cases to warn/error at compile time while providing additional information to the optimizer in release builds. Technically with whole-program builds you could get the same with regular assertions in the function implementation but I can see how having them in the function prototype could make things easier in practice.
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:
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)
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.
But C++ "struct" has many more features than C "struct". Virtual functions aka compiler-generated invisible vtables, constructors (initialization functions) and destructors (automagically called deinitialization functions), the whole templates complex of features...
I was thinking for implementing borrow-checker like rules for C but after I have seen what SMT solver can do I think it is not necessary to constrain the language in this way.
IIRC Rust's borrow checker analysis is more or less a subset of what SMT solving is capable of. It's "just" a question of tradeoffs made with respect to performance, complexity, precision, etc.
It is a really tiny and trivial subset of what model checkers can do. Model checkers can often prove memory safety of entirely unmodified C code. The problem is that this still runs into complexity problems and does not scale to non-toy programs. But this would also really be very far at the other end of the borrow checker that would require rewriting everything into a very specific form a borrow checker accepts.. IMHO a middle ground with some annotations is realistic and would be far more convenient than a borrow checker when supported by good tooling.
Your background is C as I understand it, and C does not really have issues with temporaries as they are known in C++ and Rust.
My understanding:
C compound literals have invariable and relatively long lifetimes, namely to the end of the local block, and to shorten those lifetimes, one has to put the expression with the compound literal in a new, smaller local block. The advantage of those long lifetimes is that lifetimes are typically long enough for it not to cause issues in C. C also does not have RAII.
Both C++ and Rust have varying lifetimes of temporaries and have RAII. Both C++ and Rust have had messes with lifetimes, C++23 changed lifetime rules for the range-initializer expression of range-based for-loops, and Rust changed lifetimes rules in Rust 2024 such that whether a specific piece of Rust code has a deadlock or not, depends on its edition, and editions are generally not specified per-file but per-project.
Temporary lifetime extension can be tricky for both developers and language designers.
But the borrow checker can make it worse in some situations, for it can push developers towards bug prone designs that have greater risks of deadlocks, just to satisfy the borrow checker. The borrow checker also mostly enabled the language designers to go wild with lifetimes, since they did not have to worry as much about UB as long as they were in the not-unsafe subset, so the issues with deadlocks might be worse in practice than if they had been more careful and restrained with lifetimes.
It can also be very subtle whether Rust code has deadlocks or not. One of these Rust programs has a deadlock, the other does not. The difference is the presence and absence of temporary lifetime extension, as per the "{}".
I guess this must be the fearless concurrency I am hearing about. /s
In general I understand why people like RAII and the idea of not worrying about pesky details such as resource cleanup is appealing, but the philosophy in C is that the compiler should not do such things behind your back and perhaps this is sometimes valuable. I like this more because things are explicit and visible and I rather want to have tools that tell me that what I do is correct rather than a language that does a lot of things implicitly behind my back (especially if this is correct 99% of times but sometimes surprisingly is not). This is why I am not a fan of C++ nor Rust. Thank you for the nice example illustrating issues in Rust arising from this.
I don't like the "fearless concurrency" motto, because it often gets discussed in isolation of all kinds of concurrency and ways to access resources in systems languages.
It suffices that the resource being accessed concurrently isn't inside in same memory space like shared memory, mapped regions, a database connection, where the Rust type system can do very little to prevent other processes, or conflicting SQL queries without proper transactions, to mess up with the data coherency.
> The problem is that this still runs into complexity problems and does not scale to non-toy programs.
Right; that is why Rust's borrow checker was designed to be intraprocedural only. That obviously is a tradeoff of its own: now functions need to be annotated to convey information across function boundaries, and the annotations picked will influence what can and cannot be expressed.
> IMHO a middle ground with some annotations is realistic and would be far more convenient than a borrow checker when supported by good tooling.
I'd be interested in seeing what such a middle ground looks like, particularly in respect to what tradeoffs it makes!
I don't know yet. My current expectation from what I have seen is that lifetime analysis can be done fully automatic for most C code as long as it can be done independently from other program state, i.e. the checker has to reason only about the state of pointers and not also simultaneously about all other variables. I have seen quite promising work on this.
Thanks for the link! That's an interesting approach indeed. If I'm reading the paper [0] correctly the concepts involved look pretty similar to what Rust uses. I think they turn the interprocedural analysis into an intraprocedural one as well by effectively deriving borrowing information from function implementations and attaching that to signatures for use by callers. It's a compatibility hazard, which is why Rust chose to not do the same, but in principle it makes it easier to derive more precise borrowing information.
They globally derive the borrowing information for all pointers in the whole program by classifying the role of each pointer. If there is a way to do this in a way that makes the program safe, this is computed automatically using a SAT solver.
Yeah, the problem with the lifetimes profile vision, is the lack of acknowledgement of how MSVC and clang-tidy have struggled to achieve the Core Guidelines ideas for lifetimes (since 2015), without additional annotations.
MSVC requires SAL and clang has equally added extensions for type annotations.
The WG21 politics killed the Safe C++ proposal with the P3466 paper,
"(Re)affirm design principles for future C++ evolution"
> Do not add a feature that requires viral annotation
However it is quite clear, that without changing language semantics, for the lifetimes profile to be actually useful, as shown by MSVC and clang experience, we would need to have [[profile:lifetime(something)]] in plenty of places.
Not only would that be more verbose than what Rust and Safe C++ proposal, it would still require placing those annotations on the standard library (yet another complaint from the anti-Safe C++ proposal folks), and naturally violate the paper that was voted for as the design principles for future C++ evolution.
However in regards to how to check for C and C++ lifetime issues, at least if the source code is fully available, I have had some good experience with AI tools doing code review, like CoPilot on Visual Studio.
Maybe that is possible way forward, moreso if the code isn't being fully written by humans.
Regarding C specifically, what about having Frama-C's ACSL as some kind of ISO C annex?
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.
That seems like the reasoning that given the amount of UNIX and C code in the industry, memory corruption bugs only happen to those that don't understand their job.
Having gotchas is natural in language design, often times they only surface after field experience.
The problem with many Go's gotchas is the initial refusal to learn how other languages approached the same problems, the gotchas they got in the process, only to come up with some patch work afterwards, e.g. pseudo-exceptions with panic/defer/recover, rewriting import urls in go.mod,...
This is just how I've felt writing both Rust and C++. Rust's language complexity often feels like it's there so that I don't have to think about so much complexity when interfacing with the code, whereas C++'s language complexity introduces footgun after footgun and I have to think very carefully about what everything could possibly do (e.g. if destructors even get run in certain instances when I'd expect them to).
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.
> there are so many subsets of that language you never have to leave it.
I wouldn't call it "subsets" as much as support for "Multiple Paradigms" in the same language. They can all be used together to express powerful abstractions. James Coplien wrote "Multi-Paradigm Design for C++" in 1998 (before C++11!). Then people figured out Template metaprogramming etc. and the design space became even more wider.
Today with all the available language features and library additions you never have to leave the language at all whatever be the platform(cpu-arch+OS) you are working on. Add-in trivial interop with C and there is nothing out of bounds.
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.
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.
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.
Unless you are doing trivial boolean comparisons, I always prefer a custom assert function that will also print a useful error message. Just telling me "Assertion failed: index < container.size()" isn't very helpful. What were the values compared?
Instead, my custom asset function (in C) looks like this:
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'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 hadn't seen that talk, but the content is just an updated rehash of this is what contracts can do.
What I want I'd : we have used contracts in the tall world. This is what works and doesn't. This catches a lot of bugs for little work. This looks the same but results in false positives. This is a lot of work for few bugs.
Of course since I don't know what works I'm guessing, but that type of thing.
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 wonder what are the use cases in their mind when designing this.
For runtime assertions and documentations, yeah, it would work but it works without contract arguably ok.
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've spent years avoiding lisp, I am not about to break that streak now.
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.
The tooling around S-expression syntaxes is almost necessarily better than others because it's so straightforward, eg, structure-based editing operations. The "problem" with IDE integration for such languages is generally the (lack of) availability of static information, especially due to Racket's expressive arbitrarily-many-phased macro system, but that's a separate axis of language design.
That said, the parent comment explicitly pointed out Rhombus as an alternative to an S-expression syntax within that same community, so maybe you could look into that suggestion instead of being pithy about topics in which you are less well informed.
I suspect the GP might not like Rhombus because it uses indentation (like Python) for code blocks instead of braces (which isn't actually Algol-like; IIRC, Algol mostly used parentheses). In that case, even Pyret wouldn't work for them.
Personally, I'm still (25 years into programming) undecided: does syntax not matter at all, matter only a bit, or is one of a language's most important characteristics? It's incredibly hard to say, at least for me.
check the condition, write out the msg, maybe dump a backtrace if that works in your codebase, call abort();.
assert() is just kind of nasty, why use it?
precondition in the function sig is just "We know you don't document and nobody reads it if you do." Which is great, but not worth the control you get writing your own ASSERT_ALWAYS() and being able to s/_ALWAYS//if the performance in release matters.
For libraries there is some value in using the standard interface instead of inventing your own that users then have to deal with. I agree that assert() could be better though.
On the other hand, even the C++ stdlib implementations have their own assert handlers so...
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.
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.
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.
At least for this iteration of contracts, no. From the proposal (italics in original) [0]
> Features Not Proposed
> To keep the scope of this proposal minimal (while still viable), the following features are intentionally excluded from this proposal; we expect these features to be proposed as extensions at a later time, building on the foundations laid by this proposal.
> The ability to assume that an unchecked contract predicate would evaluate to true and to allow the compiler to optimize based on that assumption, i.e., the assume semantic
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 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 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.
If your code which "cannot fail" actually fails, terminating the program seems entirely reasonable. All logic could be wrong at that point, and it's better to crash loudly and fix the issue than to try to limp along with logic that probably won't work.
Same uses as assert, but more options for what to do when they fail and you can move them (pre/post at least) into the function declaration so they act as a kind of executable documentation (when you compile the program so that they are executed and not dropped).
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.
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.
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.
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.
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.
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 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.
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.
This is why in my own code I define macros assume and require. The former documents assumptions, asserts for debug builds, and calls __builtin_unreachable for production builds. The latter documents validated requirements, always runs, and is guaranteed to produce a core dump if violated.
Nomenclature is important.
> 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
The consequence of being required is the standard is limited to exactly what we are sure everybody can do today. If there are advances in proving C++ code the new advances cannot be used to tell you about more bugs.
That's like compiling with -Wall. Stuff will keep breaking randomly, so people will just disable it.
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.
For my own code I generally -Wall -Werror and if that breaks a prospective dependency in a nontrivial manner I find a replacement. I'll also tend to go looking for a replacement if a prospective dependency breaks when compiled with either asan or ubsan. Why would I voluntarily subject myself to such shoddy code?
I perform my own builds with -Wall -Werror. But it is a kindness to users of your code to provide a build flag that excludes -Werror. This allows user who are building with a GCC compiler version later than mine to sill build the code without having to modify the build scripts.
https://news.ycombinator.com/item?id=50108112
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.
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 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.
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.
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.
You may have to internally use bignums since collatz orbits can become very large before shrinking.
That's a good point. I wonder how difficult comptime bignum would be in comptime. Seems like there's no proven bounds I could find either
> If you wanted a c++26 preconditon for collatz tho, here it is.
You’ll need to add code to detect cycles other than 1, 4, 2, 1, 4, 2,… to that. That can be done in constant space (https://en.wikipedia.org/wiki/Cycle_detection#Floyd’s_tortoi...)
Ideally, you also need a way to detect runs towards infinity. That is harder.
Otherwise, an optimising compiler can use the forward progress guarantee and optimize that to:
and then completely remove the precondition.
I think I saw years ago that clang replaced a collatz impl with true. Why is it that the "forward progress garuntee" would let it do that?
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.
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/.
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
FTFY: "Most bugs happen in the space of things that the programmer thinks can't happen but happen anyways". I'm sure we all have stories about some baffling error that simply couldn't have occurred, it's impossible, and then you spent an hour staring at the code, reading the tracing logs, and then finally finding a single-character typo responsible for it. Whoops, it's not supposed to be "if (p != nil) { return false; }", it's meant to be "if (p == nil) { return false; }"!
But yeah, that's precisely what contracts are (at least originally) intended to be, but you can quite clearly see from this comment section that this is not how they're going to be used. Well, this surely gonna be "fun" experience down the line for the C++ programmers...
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?
Because then they wouldn't have contracts in C++.
Preconditions cannot be checked at compile time in all cases.
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.
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:
vs
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.
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
No, it's not. Don't do that. Someone (like me) will replace assert.h wholesale and it will not behave the way you expected. Always define your own internal macro and always provide facilities for the user to override it.
> Always define your own internal macro and always provide facilities for the user to override it.
that kinda sounds even more insane and cumbersome
It's literally standard practice. You import assert.h unless the end user has defined mylib_assert in which case you don't import and use that instead. Failing to do this can result in all kinds of dumb problems thanks to the namespace being global.
This is totally backward. It's literally not any standard practice I have ever seen. In fact, it is an anti-practice. If you go around re-#defining standard library macros then you get what you deserve, and get no sympathy from me.
And it seems completely sensible that if I'm writing a C++ library that it's header files should include assert.h, and use assert()'s in its inline functions. And you will get no sympathy from me if you break my code by redefining the library behavior of assert().
Blindly replacing #include <assert.h> to look up something incompatible that doesn't respect NDEBUG seems like a remarkably bad idea
I don't see why. The user invoking the compiler should be the only one controlling NDEBUG (or its equivalent). So far the only bad idea I've run into is the occasional library author blindly importing assert.h which happily stomps all over my custom implementation.
There's a whole host of things that libraries need to be extremely conservative about touching. Assert, threads, signals, etc.
That's not what the C/C++ spec says people can rely on with assert/NDEBUG. Like don't get me wrong, you can do what you want, but you can and will break code in ways that introduce memory unsafety with this approach
> Of course?
You sound as if "my asserts will all always be run via NDEBUG" makes perfect sense and not the exact opposite of what you've expanded it to
> Its common practice
not really, no
post ++11 cpp has been moving away from preprocessor (pedagogically and culturally), so "let's force-undefine NDEBUG" trick isn't at the top of most people's minds
You use the status of NDEBUG to control whether or not your asserts are run. I don't understand how this is complex
If you want to use asserts, you need to control the definition of NDEBUG. You either do this with your build system (common), or manually (slightly less common). This is literally <assert.h> 101
The preprocessor is widely used for a variety of things when its necessary, including #includes and #defines, because there's literally no other mechanism for this. Its bizarre to say that nobody controls the definition status of NDEBUG because people don't use the preprocessor
I don't know if it's good practice but I replace assert and assert.h with my own code (you just have to make sure to make the necessary platform specific calls in your replacement headers). So I always know exactly how assert will behave in my own codebase even when invoked by a library.
It's worth noting that the motivation for the above was poorly written libraries that hardcode the use of the builtin assert. So I agree with you completely on that point.
That said I've never been particularly impressed with how assert is defined by the standard and now somehow contracts appear set to be even worse.
Now you know. It's VERY bad practice.
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.
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
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.
>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.
I used to be a big fan of contracts, and even implemented it in the Zortech C++ compiler back in the 90's:
https://www.digitalmars.com/ctg/contract.html
I implemented it in D as well. But, it just turned out to not be very useful.
Can you go into why you didn't find them to be useful? Seems if done right they should allow many cases to warn/error at compile time while providing additional information to the optimizer in release builds. Technically with whole-program builds you could get the same with regular assertions in the function implementation but I can see how having them in the function prototype could make things easier in practice.
`assert` turned out to be much more useful.
The only advantage contracts had was with inheritance hierarchies, and I don't use them enough to make it pay off.
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
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.
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)
I don't get the impression that these are meant to influence optimization at all.
That is not a primary goal. There was a discussion about "assume" semantics, but that is not one of the standardized contract evaluation semantics.
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.
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.
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.
But C++ "struct" has many more features than C "struct". Virtual functions aka compiler-generated invisible vtables, constructors (initialization functions) and destructors (automagically called deinitialization functions), the whole templates complex of features...
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.
I was thinking for implementing borrow-checker like rules for C but after I have seen what SMT solver can do I think it is not necessary to constrain the language in this way.
IIRC Rust's borrow checker analysis is more or less a subset of what SMT solving is capable of. It's "just" a question of tradeoffs made with respect to performance, complexity, precision, etc.
It is a really tiny and trivial subset of what model checkers can do. Model checkers can often prove memory safety of entirely unmodified C code. The problem is that this still runs into complexity problems and does not scale to non-toy programs. But this would also really be very far at the other end of the borrow checker that would require rewriting everything into a very specific form a borrow checker accepts.. IMHO a middle ground with some annotations is realistic and would be far more convenient than a borrow checker when supported by good tooling.
A borrow checker can do more harm than good in some cases. An arguable example of that is https://fasterthanli.me/articles/a-rust-match-made-in-hell .
Your background is C as I understand it, and C does not really have issues with temporaries as they are known in C++ and Rust.
My understanding:
C compound literals have invariable and relatively long lifetimes, namely to the end of the local block, and to shorten those lifetimes, one has to put the expression with the compound literal in a new, smaller local block. The advantage of those long lifetimes is that lifetimes are typically long enough for it not to cause issues in C. C also does not have RAII.
Both C++ and Rust have varying lifetimes of temporaries and have RAII. Both C++ and Rust have had messes with lifetimes, C++23 changed lifetime rules for the range-initializer expression of range-based for-loops, and Rust changed lifetimes rules in Rust 2024 such that whether a specific piece of Rust code has a deadlock or not, depends on its edition, and editions are generally not specified per-file but per-project.
Temporary lifetime extension can be tricky for both developers and language designers.
But the borrow checker can make it worse in some situations, for it can push developers towards bug prone designs that have greater risks of deadlocks, just to satisfy the borrow checker. The borrow checker also mostly enabled the language designers to go wild with lifetimes, since they did not have to worry as much about UB as long as they were in the not-unsafe subset, so the issues with deadlocks might be worse in practice than if they had been more careful and restrained with lifetimes.
It can also be very subtle whether Rust code has deadlocks or not. One of these Rust programs has a deadlock, the other does not. The difference is the presence and absence of temporary lifetime extension, as per the "{}".
https://play.rust-lang.org/?version=stable&mode=debug&editio...
https://play.rust-lang.org/?version=stable&mode=debug&editio...
I guess this must be the fearless concurrency I am hearing about. /s
In general I understand why people like RAII and the idea of not worrying about pesky details such as resource cleanup is appealing, but the philosophy in C is that the compiler should not do such things behind your back and perhaps this is sometimes valuable. I like this more because things are explicit and visible and I rather want to have tools that tell me that what I do is correct rather than a language that does a lot of things implicitly behind my back (especially if this is correct 99% of times but sometimes surprisingly is not). This is why I am not a fan of C++ nor Rust. Thank you for the nice example illustrating issues in Rust arising from this.
I don't like the "fearless concurrency" motto, because it often gets discussed in isolation of all kinds of concurrency and ways to access resources in systems languages.
It suffices that the resource being accessed concurrently isn't inside in same memory space like shared memory, mapped regions, a database connection, where the Rust type system can do very little to prevent other processes, or conflicting SQL queries without proper transactions, to mess up with the data coherency.
LLM reply?
Yeah an LLM with an unique neural networks biological model designed in Portugal at the edge of Portugal's newly found democracy.
> The problem is that this still runs into complexity problems and does not scale to non-toy programs.
Right; that is why Rust's borrow checker was designed to be intraprocedural only. That obviously is a tradeoff of its own: now functions need to be annotated to convey information across function boundaries, and the annotations picked will influence what can and cannot be expressed.
> IMHO a middle ground with some annotations is realistic and would be far more convenient than a borrow checker when supported by good tooling.
I'd be interested in seeing what such a middle ground looks like, particularly in respect to what tradeoffs it makes!
I don't know yet. My current expectation from what I have seen is that lifetime analysis can be done fully automatic for most C code as long as it can be done independently from other program state, i.e. the checker has to reason only about the state of pointers and not also simultaneously about all other variables. I have seen quite promising work on this.
> lifetime analysis can be done fully automatic for most C code as long as it can be done independently from other program state
Is this an interprocedural or intraprocedural analysis?
> I have seen quite promising work on this.
Is this work public? I'm interested in seeing more!
It is interprocedural, but one obviously needs annotations for interfaces to libraries that are linked-in.
One example of a project wich goes in this direction is this work: https://www.sei.cmu.edu/projects/ai-powered-memory-safety-fo...
(I assume the AI is mostly marketing as it is not necessary)
Thanks for the link! That's an interesting approach indeed. If I'm reading the paper [0] correctly the concepts involved look pretty similar to what Rust uses. I think they turn the interprocedural analysis into an intraprocedural one as well by effectively deriving borrowing information from function implementations and attaching that to signatures for use by callers. It's a compatibility hazard, which is why Rust chose to not do the same, but in principle it makes it easier to derive more precise borrowing information.
[0]: https://dl.acm.org/doi/epdf/10.1145/3814943.3816182
They globally derive the borrowing information for all pointers in the whole program by classifying the role of each pointer. If there is a way to do this in a way that makes the program safe, this is computed automatically using a SAT solver.
Yeah, the problem with the lifetimes profile vision, is the lack of acknowledgement of how MSVC and clang-tidy have struggled to achieve the Core Guidelines ideas for lifetimes (since 2015), without additional annotations.
MSVC requires SAL and clang has equally added extensions for type annotations.
The WG21 politics killed the Safe C++ proposal with the P3466 paper,
"(Re)affirm design principles for future C++ evolution"
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p34...
> Do not add a feature that requires viral annotation
However it is quite clear, that without changing language semantics, for the lifetimes profile to be actually useful, as shown by MSVC and clang experience, we would need to have [[profile:lifetime(something)]] in plenty of places.
Not only would that be more verbose than what Rust and Safe C++ proposal, it would still require placing those annotations on the standard library (yet another complaint from the anti-Safe C++ proposal folks), and naturally violate the paper that was voted for as the design principles for future C++ evolution.
However in regards to how to check for C and C++ lifetime issues, at least if the source code is fully available, I have had some good experience with AI tools doing code review, like CoPilot on Visual Studio.
Maybe that is possible way forward, moreso if the code isn't being fully written by humans.
Regarding C specifically, what about having Frama-C's ACSL as some kind of ISO C annex?
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.
Just because you can easily understand something doesn't mean it isn't a footgun.
That seems like the reasoning that given the amount of UNIX and C code in the industry, memory corruption bugs only happen to those that don't understand their job.
Never used go, but I assume interface is some kind of indirection? If so, this doesn't seem anymore confusing than:
> 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.
Yes, I meant that with my mention of rewrites, and it is design wart that such workarounds are required in first place.
None of those are real problems in reality, I don't think Go has more gotchas than other languages.
Having gotchas is natural in language design, often times they only surface after field experience.
The problem with many Go's gotchas is the initial refusal to learn how other languages approached the same problems, the gotchas they got in the process, only to come up with some patch work afterwards, e.g. pseudo-exceptions with panic/defer/recover, rewriting import urls in go.mod,...
This is just how I've felt writing both Rust and C++. Rust's language complexity often feels like it's there so that I don't have to think about so much complexity when interfacing with the code, whereas C++'s language complexity introduces footgun after footgun and I have to think very carefully about what everything could possibly do (e.g. if destructors even get run in certain instances when I'd expect them to).
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.
> there are so many subsets of that language you never have to leave it.
I wouldn't call it "subsets" as much as support for "Multiple Paradigms" in the same language. They can all be used together to express powerful abstractions. James Coplien wrote "Multi-Paradigm Design for C++" in 1998 (before C++11!). Then people figured out Template metaprogramming etc. and the design space became even more wider.
Today with all the available language features and library additions you never have to leave the language at all whatever be the platform(cpu-arch+OS) you are working on. Add-in trivial interop with C and there is nothing out of bounds.
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.
This is a cool way to think about it!
C++ as a reference language. That’s actually a great concept.
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/
That "test bed" description fits Rust way better. Rust is in many ways also significantly more complex than C++, believe it or not.
rustc is also awfully shoddy. https://github.com/rust-lang/rust/issues/164348#issuecomment...
> That "test bed" description fits Rust way better.
This assertion seems rather debatable given Rust's general lack of trailblazing in the language design space.
> Rust is in many ways also significantly more complex than C++, believe it or not.
I'm a bit skeptical that "many" is the right qualifier there. "Some", perhaps, but it isn't obvious to me how you'd reach "many".
In which way is Java a language-feature testbed?
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.
https://news.ycombinator.com/item?id=50108112
"Verified Design-By-Contract" has been implemented for C/C++ (annotated subset) by David Crocker in his Escher C/C++ Verifier - https://www.eschertech.com/products/ecv.php and https://www.eschertech.com/products/verified_dbc.php
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
Unless you are doing trivial boolean comparisons, I always prefer a custom assert function that will also print a useful error message. Just telling me "Assertion failed: index < container.size()" isn't very helpful. What were the values compared?
Instead, my custom asset function (in C) looks like this:
Example:
If the first parameter is false, a detailed error message is printed:
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.
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)
This was the latest where I saw him mentioning it.
"Our Journey Toward a Fully Backward-Compatible, UB-Safe, ISO C++"
https://youtu.be/whFzmXXhG5g?is=jSOnYkqr5nNULGkY
I hadn't seen that talk, but the content is just an updated rehash of this is what contracts can do.
What I want I'd : we have used contracts in the tall world. This is what works and doesn't. This catches a lot of bugs for little work. This looks the same but results in false positives. This is a lot of work for few bugs.
Of course since I don't know what works I'm guessing, but that type of thing.
Since this is for C++26, you use modules.
I live mostly in the real world, unfortunately.
If new major C++ features will require modules in order to work, I may as well stop following the specs.
In the real world, VC++ and clang with MSBuild, CMake/ninja already have good modules support, GCC 16 only has a few issues to iron out.
By the time C++26 actually becomes available, all three major compilers will already have good modules support.
Anyone using recent Office versions, is a consumer of a product that makes use of C++ modules today.
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 wonder what are the use cases in their mind when designing this. For runtime assertions and documentations, yeah, it would work but it works without contract arguably ok.
For formal verification, the design is way off.
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've spent years avoiding lisp, I am not about to break that streak now.
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.
The tooling around S-expression syntaxes is almost necessarily better than others because it's so straightforward, eg, structure-based editing operations. The "problem" with IDE integration for such languages is generally the (lack of) availability of static information, especially due to Racket's expressive arbitrarily-many-phased macro system, but that's a separate axis of language design.
That said, the parent comment explicitly pointed out Rhombus as an alternative to an S-expression syntax within that same community, so maybe you could look into that suggestion instead of being pithy about topics in which you are less well informed.
I suspect the GP might not like Rhombus because it uses indentation (like Python) for code blocks instead of braces (which isn't actually Algol-like; IIRC, Algol mostly used parentheses). In that case, even Pyret wouldn't work for them.
Personally, I'm still (25 years into programming) undecided: does syntax not matter at all, matter only a bit, or is one of a language's most important characteristics? It's incredibly hard to say, at least for me.
doesn't everyone write their own assert macros?
check the condition, write out the msg, maybe dump a backtrace if that works in your codebase, call abort();.
assert() is just kind of nasty, why use it?
precondition in the function sig is just "We know you don't document and nobody reads it if you do." Which is great, but not worth the control you get writing your own ASSERT_ALWAYS() and being able to s/_ALWAYS//if the performance in release matters.
> assert() is just kind of nasty, why use it?
For libraries there is some value in using the standard interface instead of inventing your own that users then have to deal with. I agree that assert() could be better though.
On the other hand, even the C++ stdlib implementations have their own assert handlers so...
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.
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.
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.
Are there any public examples of contracts in use, or of the Bloomberg library that contracts were based on?
I thought this is some Hoare-logic things ported to C++, but well it's only stanzarized guard statements.
How does language design evolve where LLMs write the actual code?
Will compilers be able to optimize functions based the assumtions that contracts hold (even if they aren't being checked)?
Of course, that's the idea. At least my C compiler does so
Your C compiler optimizes based on C++ contracts?
C++ like contracts. but without the ending semicolon, because that would cause backward compatibility problems. https://github.com/rurban/rcc/#additional-c-features
At least for this iteration of contracts, no. From the proposal (italics in original) [0]
> Features Not Proposed
> To keep the scope of this proposal minimal (while still viable), the following features are intentionally excluded from this proposal; we expect these features to be proposed as extensions at a later time, building on the foundations laid by this proposal.
> The ability to assume that an unchecked contract predicate would evaluate to true and to allow the compiler to optimize based on that assumption, i.e., the assume semantic
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.
No worries, this will probably land in C as well, static_assert was already.
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.
Yet more language bloat
Ah Eiffel!
C++ == --D
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
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 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.
If your code which "cannot fail" actually fails, terminating the program seems entirely reasonable. All logic could be wrong at that point, and it's better to crash loudly and fix the issue than to try to limp along with logic that probably won't work.
What are they even good forbid we can’t validate the input with? A convenient way to write down parameter ranges. Nothing more
Same uses as assert, but more options for what to do when they fail and you can move them (pre/post at least) into the function declaration so they act as a kind of executable documentation (when you compile the program so that they are executed and not dropped).