D supports contract programming, which can help you ensure an object's state is always valid and object methods are passed with correct parameters and returns valid values, including while using class inheritance. Here, we'll see how to use these features and why they work the way they do.
Perform the following steps:
interface
, class
, or struct
, add in
and out
blocks to methods, right after the signatures, and also add invariant
blocks to the aggregate.in
blocks to verify your preconditions.out
blocks to verify your postconditions. The out
block may take an argument to access the method's return value.body
keyword before your method's body but after its in
and out
blocks (if it has them).in
contracts to reassert your input requirements, even if they are the same as the parent class.invariant
blocks to verify object conditions that are always true. The invariant must be valid as soon as the constructor returns.–release
switch and running the program.The code is as follows:
interface Example { int negate(int a) in { assert(a >= 0); } out(ret) { assert(ret <= 0); } // note: no body since this is an interface } class ExampleImplementation : Example { int negate(int a) in { assert(a >= 0); } // same input restriction body { // note the body keyword following the contracts return –a; } invariant() { // we have no data, so no need for any invariant assertions } } void main() { auto e = new ExampleImplementation(); e.negate(-1); // throws an assertion failure }
D supports contract programming, inspired by the Eiffel language, with three constructs: the in
and out
blocks on methods and invariant blocks in objects.
All contracts are run at runtime. Running contracts at compile time is not possible in D today. Like other assert
statements, contracts are compiled out when compiling in release mode.
It's preferable to use the in
and out
blocks instead of writing assertions inside the function itself, because the in
and out
blocks understand inheritance. When overriding an inherited method in a class, the following points must be considered:
in
blocks in the inheritance tree must pass (it may weaken preconditions by providing alternate checks). If there is no in
block on a method, it is assumed to accept any input, and thus always pass. This is why child classes have to reassert their input requirements, even if they are the same as the interface.out
blocks in the inheritance tree must pass (it may strengthen postconditions by providing additional checks).invariants
on the inheritance tree must pass (it must honor the parent's invariants
).These rules realize the Liskov substitution principle of object-oriented programming; a subclass should always be substitutable for its superclass or interface without breaking any code. When using an object through its interface, only the interface's in
blocks are considered and it must pass (this is because the interface doesn't know which implementation it is forwarding to).
A limitation of contracts on methods in D is the lack of a pre-state in the out
contract. There is no easy way to work around this limitation at this time. Instead, focus your out
contracts on ensuring that your return value alone meets some requirements, such as not being null.
The current D spec does not permit contracts on abstract class
methods as well (though they are permitted on interfaces). This limitation may be removed in the future.
Since contracts are logically part of the interface, when you write a library, you should also write your contracts in your D interface file (a .di
file, which includes function prototypes but not the whole implementation bodies), if you choose to use them. This way, the user or compiler may choose to insert them in a debug build of a program, even when using a release build of the library. However, at this time, the compiler does not keep contracts. The common solution is to provide both debug and release builds of your library or to distribute full source code and let the user build it themselves.
18.118.140.204