On Wednesday, October 12, 2016 20:40:57 mikey via Digitalmars-d-learn wrote:
> OK I'm somewhat struggling with this concept.

It is a bit weird. But consider

class A
    auto foo(int i)
    in { assert(i < 10); }
    out(r) { assert(r > 20); }
    body {...}

class B : A
    override auto foo(int i)
    in { assert(i < 42); }
    out(r) { assert(r > 30); }
    body {...}

A a = func();

a could be an A or a B. If it's an A, then the argument has to pass the in
countract on A.foo, and the contract on B.foo doesn't matter, because it's
not a B. And if a is actually a B, then the argument must _still_ pass
A.foo's contract, because it's unknown from the caller's perspective whether
it's an A or a B or some other class derived from A. B.foo can't have a
tighter contract than A.foo, because that would violate the requirements of
A.foo. If it were otherwise, if func() were changed to return a B instead of
an A, the code would break even though it's using A's API and not B's. If
B.foo required that i be less than 5, then suddenly code that worked with 6
would fail because of an in constraint that wasn't part of the API being
used. So, tighter in contracts on derived class functions violate the
contract of the base class function and cannot be allowed.

However, B.foo _can_ have a looser in contract than A.foo, because if you're
using it as an A, then everything that you can pass to A still works, and if
you're using it as a B, and it accepts more than an A, so what? That's not a
problem. You're using B's API and its requirements, not A's. So, by ||ing
A.foo's and B.foo's in contracts, it allows B.foo to have a weaker contract,
but it still requires that A.foo's contract be true.

An out contract, on the other hand, is about guaranteeing that the result
meets certain conditions. Whether the object is an A or a B, calling foo on
an A reference must give a result that meets the out contract of A.foo. And
B.foo's out contract cannot widen the conditions, otherwise, it would
violate the out contract for A.foo. It can make them stronger, because it
would still meet the requirements of A.foo, but it can't make them weaker.
And if B.foo's out contract is stronger than A.foo's out contract, then when
the object is a B, it has to meet both contracts, not just one of them,
because if it just met A.foo's contract, then it would be violating the
out contract on the function actually being called, but it still has to meet
A.foo's contract, not just B.foo's contract, because if it doesn't, then
when you the B is referred to be an A reference, it would be violating A's
API. So, the out contracts get &&ed together.

Of course, you can always craft contracts that cause problems, but the
compiler and runtime try and ensure that your contracts follow the logic
required for a derived class to work correctly when referred to by a
reference for a base class and while ensuring that the derived class does
not violate the requirements of the API of the base class when the typeo of
the reference is the derived class.

>          override @property void wage(uint wage)
>          in {
>              enforce(wage >= 10_000 && wage <= 40_000);
>          } body {
>              _wage = wage;
>          }
>      }

Contracts are supposed to use assertions, since they're for checking code
validity. And using exceptions doesn't buy you anything, because contracts
get compiled out in -released just like the assertions do. You're just
making it so that your function can't be nothrow (though there's an open
bug - https://issues.dlang.org/show_bug.cgi?id=13123 - for the compiler not
catching that a nothrow function can throw thanks to contracts).

If you're trying to check the input and have the function throw so that the
program can try and recover from the bad input rather than treating it as a
logic error, then you need to throw an exception from inside the function
body and not the in contract.

- Jonathan M Davis

Reply via email to