The contract states that it is the callers responsibility to ensure that the pre-conditions are met.
The contract states clearly who is responsible for a bug. If you fail to meet a pre-condition its the caller. If you fail to meet a post-condition its the callee. That is useful enough alone that its worth documenting the contract.
Sometimes you can write your code so that the pre-conditions don't need to be checked. For example:
Foo()
{
int x = 1;
Bar(x);
}
Bar(int x) [[expects: x>0]]
{
}
You set x so you know it can't be less than zero.
At other times you do need to check them. It does sometimes create duplication . I have not found this to be a significant problem often but you may sometimes see patterns like:
SafeBar(int x)
{
if (x <= 0) throw SomeException();
else Bar(x);
}
This assumes of course that errors can be handled in the same way for each use, which is not always the case.
Removing pre-condition checks is a performance optimisation. As we know premature optimisation is the root of all evil, so it should only be done when necessary.
Now another factor is implementation. Few languages support checking contracts at compile time. It was recently voted into C++20 but at the time of writing there is only an experimental implementation.
C++20 uses attributes as above. Attributes are not supposed to change runtime behaviour.
If you don't have compile time support you will typically find implementations using some sort of assertion macro. Personally I use one that throws an exception. You are then using standard exception handling mechanism for handling bugs (some consider this inappropraite) but you don't necessarily need to check the contract at the call site.
Why might it be inappropriate? It is worth remembering that a contract violation is a bug. If you run the function without meeting the pre-condition you are invoking undefined behaviour. In principle anything could happen. It could even format your hard-drive (though that is unlikely). Checking the pre-condition at runtime is like defensive coding. If the assertion causes an exception then undefined behaviour never occurs. That is safer and makes it easier to debug. But from one point of view you've modified the contract.
In general checking contracts at compile time is undecidable. Quoting the linked answer:
If the Theorem Prover can prove that a contract will always be
violated, that's a compile error. If the Theorem Prover can prove that
a contract will never be violated, that's an optimization.
Proving contracts in general is equivalent to solving the Halting Problem and
thus not possible. So, there will be a lot of cases, where the Theorem
Prover can neither prove nor disprove the contract.
In that case, a runtime check is emitted
A slight aside as the question is marked language agnostic but an issue I have with the C++20 proposal is that it seems to omit the runtime check for the other cases. It also says explicitly that it should not be possible to set the violation handler at run time:
There should be no programmatic way of setting or modifying the violation handler
It also mandates default choice of calling std::terminate() on contract violation to end the whole process. This would be a bad thing(tm) for something like a multithreaded fault tolerant task scheduler. A bug in one task should not kill the whole process.
I think the reasoning is that C++20 contracts are intended as a compile time feature only. That includes evaluating them in compile time meta-pograms using constexpr and consteval. The feature allows compiler venders to start adding theorem provers to check contracts which was not possible before. This is important and opens up many new opportunities.
Hopefully a pragmatic modification considering the runtime possibilities will follow.
The downside is that in the short term you will need to retain your assertions. If, like me, you use Doxygen for documentation (which does not yet understand contracts) you have triple redundancy. For example:
///
/// @brief
/// Bar does stuff with x
///
/// @pre
/// @code x > 0 @endcode
///
void Bar(int x) [[expects: x > 0]]
{
{ //pre-conditions
assertion(x>0);
}
...do stuff
}
Note that the C assert() macro doesn't throw. Hence we use our own assertion() macro that does. The CppCoreGuidelines support library includes Expects() and Ensures(). I'm not sure if they throw or call std::terminate().