For C you can have a look at Frama-C. [2]
D also has DbC. [3]
In case you can target Windows only, VC++ supports SAL Annotations, while not DbC they help to improve code security
[1] - http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p233...
[2] - https://frama-c.com/html/acsl.html
[3] - https://dlang.org/spec/contracts.html
[4] - https://docs.microsoft.com/en-us/cpp/code-quality/using-sal-...
Specifically, do the language semantics allow a mode where these checks can be turned off? Leaving them on all the time can be a huge performance burden, both directly and in the barriers they add to optimization (while there are potential optimization benefits to exploiting the guarantees of contracts, I don’t think as a practical matter they balance out).
But allowing them to be turned off in “release mode” eliminates much of the benefit in all modes: Developers can no longer assume that their code always runs in a context where the contractual guarantees are met, so they have to guard data integrity or safety critical operations dynamically anyway.
Yes, with Ada it is typically a pragma or compiler-flag to turn checks off; however, Ada has a long history of having mandatory-checks and strongly-encouraging compiler-implementers to optimize the checks away when it is known they cannot fail (which is a surprising chunk of the time if you're coming from a C-based language).
I think the performance impacts are usually not as big as you think (the Ada compiler is smart enough to eliminate redundant checks where it can), and I believe that they can be turned off per package, too, so for performance critical modules they can be deactivated in release mode.
I do not think contracts were intended to raise recoverable errors in a normal application's behavior; if they are triggered, then the program is incorrect, not just encountering an error. I think most developers would leave them on in release mode (if possible) just to get stack traces and exception messages to see what failed.
My thumb rule is to have all preconditions/postconditions/invariants turned on only in the boundary functions (i.e. public api) of a module while the inner cohesive functions only have preconditions turned on.
As the team leader for the second version of a database middleware product developed in C, being convinced by study of the efficacy of assertions for DbC and hence better quality software, I made sure my team used C assertions heavily throughout the codebase, at the entry and exit points of functions, to implement preconditions and postconditions, despite some opposition to it.
End result: the product was a success, and was used in multiple software projects for customers.
We were rewarded well for it.
Edit: I first learned about DbC myself, via reading about Eiffel and Bertrand Meyer's work, early on.
I highly recommend reading Bertrand Meyer's papers "Applying Design By Contract" and "Design by Contract". They are well worth every programmer's time.
Preconditions: "a condition or predicate that must always be true just prior to the execution of some section of code or before an operation"[1]. It is up to the caller (client) to set up the preconditions and ensure that they are true before calling the code in question. If any preconditions are not met, it is the fault of the caller (client).
Postconditions: "a condition or predicate that must always be true just after the execution of some section of code or after an operation"[2]. The code in question guarantees that the postconditions will be true after the code is executed. If any postconditions are not met, it is the fault of the callee (supplier).
Invariants: conditions or predicates that "can be relied upon to be true during the execution of a program, or during some portion of it"[3]. Both parties must ensure the invariants hold.
These features make it very easy to determine where a bug is in the code and make it very explicit what is expected of the caller (client) and callee (supplier).
They act as run-time sanity checks and push Ada / SPARK code in the direction of Haskell function signatures and types. With proper preconditions, postconditions, and invariants in place I think it should be possible to implement a QuickCheck-style[4] testing system to provide some empirical checks if SPARK proof checking is not used.
I would love to see these design-by-contact features added to Rust, C++, and even C.
[0] https://www.eiffel.com/values/design-by-contract/introductio...
[1] https://en.wikipedia.org/wiki/Precondition
[2] https://en.wikipedia.org/wiki/Postcondition
[3] https://en.wikipedia.org/wiki/Invariant_(mathematics)#Invari...
[4] https://en.wikipedia.org/wiki/QuickCheck