assert and enforce both compiled out with -release

Jonathan M Davis newsgroup.d at jmdavisprog.com
Sat Jan 27 18:23:58 UTC 2018


On Saturday, January 27, 2018 17:12:25 kdevel via Digitalmars-d-learn wrote:
> Then please explain to me, in which respect the advice to "Use
> assert[s] in contracs" makes sense if the contracts are to be
> compiled out. I don't get it.

The entire point of contracts is to be asserting pre or post conditions. In
some cases, there really isn't much difference between putting the
assertion in the contract from putting it in the body. e.g.

void foo(int i)
in
{
    assert(i > 42);
}
do
{
}

and

void foo(int i)
{
    assert(i > 42);
}

are pretty much the same, but it can matter. e.g. you can have additional
lines of code in a contract that can't go in a assertion:

void foo(C c, D d, int i)
in
{
    auto c = c.foo();
    sort(c);
    assert(d.bar(i) == c);
}
do
{
}

To do that in the function body, you'd either have to make it a single
expression (which in some cases is easy, and other cases can't be done), or
turn it into a function call where the result of the call gets asserted.
That particular example necessarily isn't a huge motivator for contracts,
but it can be useful.

It's more useful with out contracts, because then you can have have the
assertion in one place rather than with each return statement. e.g.

auto foo(T t)
out(retval)
{
    assert(retval.foo() > 19);
}
do
{
    if(blah)
        return baz();
    ...
    if(t.s == "str")
        return doSomething();
    ...
    return t.xyzzy();
}

However, where contracts really matter is with classes. In order for
contracts to work properly with inheritance when a function is overridden,
the in contract of a derived class cannot be more restrictive than that of
the base class. Otherwise, you wouldn't be able to call a function on base
class reference without caring what the actual class of the object was,
because you'd end up with contracts failing based on what the derived class
was. However, while the in contract for a derived function can't be made
stricter, it _can_ be made looser, since that wouldn't make any code fail
based on what the actual object type was, and there's no reason why the
derived class function couldn't work with a greater range of values than the
base class function.

Similarly, a derived function cannot have a looser out contract than the one
in the base class, because that would violate the guarantees that the base
class function makes. However, the derived function _can_ have a stricter
contract, because that doesn't violate the guarantees of the base class, and
there's no reason not to allow the derived class to be stricter about what
it outputs.

As such, with derived functions, the runtime effecively ||s all in
countracts and &&s all out contracts. In an inheritance chain, _one_ of the
in contracts needs to pass without throwing an AssertError, whereas none of
the out contracts can fail with an AssertError.

So, if you put an assertion in the in contract of a virtual function,
whether it actually has to pass or not depends on what the in contracts of
the other functions in the inheritance chain are, whereas if you put the
assertion in the function body, it always has to pass when that function is
run. However, if that function isn't run (e.g. a derived class function
doesn't call the base class function), then that assertion is never run,
whereas if it's in the in contract, it will be run so long as another in
contract hasn't already passed.

And if you put an assertion in the out contract of a virtual function, it
will always be run, regardless of whether a derived class function calls a
base class function. The only case where it wouldn't be run is if another
out contract had already failed (in which case, the AssertError killed your
program). But if you put the assertion in the function body, it will only be
run if that particular function is run (which may not happen if the derived
class function don't call the base class function).

So, the use of contracts can make a significant difference if you're dealing
with classes, but their benefits are pretty superficial outside of classes.

invariants are far more useful in that they run before and after every
public function call. So, you can assert the state of the object in one
place, and it gets tested whenever the public API is used.

Personally, I almost never use contracts. I rarely use classes, so the
benefits that contracts provide in that case would rarely help me, and in
other cases, I don't think that they provide enough value to bother. For in
contracts, you can just as easily put the assertion in the function unless
you need additional statements to prepare the condition to assert (which I
usually don't), and the contract syntax is verbose enough that I'd prefer to
not use it if I don't have to.

As for out contracts, I don't bother, because I find that it's rare that I
have a function where I can have a condition which is generically testable.
It's very common to be able to test that specific input gives specific
output but not that all output passes a particular condition. And unit tests
are the place to test that specific input gives you specific output, not the
out contract. So, I use unit tests _very_ heavily, but I don't use out
contracts much.

In principle, contracts really should be compiled in based on how the caller
was compiled, not the function being called, since in contracts are really
testing the caller (since it's the caller that provides the input being
tested), and it also makes sense for the caller to be in control of whether
the output is validated or not. This would be _really_ useful in the case of
libraries, because then the library could be compiled with -release, but
you'd get the contracts checked if you didn't compile your program with
-release. That would be a _huge_ motivator for using contracts with
libraries rather than putting the assertions inside the functions. But
unfortunately, that's not how contracts are currently implemented.

Also, some folks want the compiler to be able to statically examine the
conditions in contracts so that it can flag stuf at complie time based on
what's in the contracts. e.g. it would be great if the compiler flagged

void foo(int i)
in
{
    assert(i > 42);
}
do
{
}

void main()
{
    foo(0);
}

and gave you a compiler error. But no has implemented anything like that,
and contracts are purely runtime entities at this point.

It's also been suggested that contracts be put in the documentation so that
folks could see what they are and thus know what's valid and isn't rather
than having to write it out in the documentation. And if that were
implemented, then maybe it would be worth using contracts in some cases.

So, it _may_ be the case in the future that contracts will become more
useful, but right now, outside of classes, they're not all that useful. Some
folks do prefer to use them though, because they prefer to have the pre and
post conditions segregated out of their code.

- Jonathan M Davis



More information about the Digitalmars-d-learn mailing list