New pattern about interface contracts

Posted 2019-12-02

Core D Development Statistics

In the community

Community announcements

See more at the announce forum.

What Adam is working on

I started arsd.webview, a port of a wrapper around the webviews commonly found in operating systems. I probably won't finish it until the end of the month, even though it isn't a whole lot of code (by design - I don't want a huge package to distribute to use it).

Tip of the Week

D has a feature called contract programming, which lets you attach in and out statements to functions (and other things, but that's what I want to focus on right now) to assert various conditions about how your function is called and what it returns. Unlike plain assert statements in function bodies though, contracts work with inheritance. Though the specifics can be confusing.

1 import std.stdio;
2 import core.exception;
4 class Base {
5 	void test(int a)
6 		in(a > 20)
7 	{
8 		writeln("base class");
9 	}
10 }
12 class Derived : Base {
13 	override void test(int a)
14 	{
15 		writeln("child class");
16 	}
17 }
19 void main() {
20 	try {
21 		auto b = new Base();
22 		b.test(10); // this would throw
23 	} catch(AssertError ae) {
24 		writeln("correctly thrown!");
25 	}
27 	auto d = new Derived();
28 	d.test(10); // but this doesn't... why?
29 }

In that case, we see the base class threw due to the invalid argument, but not with the derived class. Are contracts inherited?

The answer is "yes", but they are easily ignored due to the application of the Liskov substitution principle.

D applies the "loosen params, tighten returns" guideline by implementing contracts and inheritance as Derived in || Base in and then Derived out && Base out. See:

If you don't specify a contract, D treats it as always true, in other words, the contract accepts anything. Thus, in the example above, Derived in == true, so the overall thing is true || a > 20... and now we can see why it always passes with the derived class.

Note that the in for an individual method is the combined total of all in clauses on that contract. So void foo() in(a) in(b) {} is the same as void foo() in(a && b) {} and when derived, that becomes (a && b) || (parent_clauses).

Can we explicitly indicate that we want to use the parent class' contract? Well, consider that X || B statement again... if X is true, B is skipped. But if X is false, B is always used and determines the overall condition. Thus, we get a new pattern:

in(false) // do not expand inputs; inherit parent conditions

Let's apply it to the test:

1 import std.stdio;
2 import core.exception;
4 class Base {
5 	void test(int a)
6 		in(a > 20)
7 	{
8 		writeln("base class");
9 	}
10 }
12 class Derived : Base {
13 	override void test(int a)
14 		in(false) // use parent's contract
15 	{
16 		writeln("child class");
17 	}
18 }
20 void main() {
21 	try {
22 		auto b = new Base();
23 		b.test(30); // OK
24 		b.test(10); // this would throw
25 	} catch(AssertError ae) {
26 		writeln("correctly thrown!");
27 	}
29 	try {
30 		auto d = new Derived();
31 		d.test(30); // still OK
32 		d.test(10); // and now this throws too
33 	} catch(AssertError ae) {
34 		writeln("correctly thrown again!");
35 	}
36 }

The syntax is a little bit weird - in(false) might take some getting used to - but if you know the reasoning above it makes sense, and if you think of the contracts in an overridden function as being "expand the input", in(false) could perhaps mean "do not expand the input".

It doesn't *quite* mean that all the time, which is why I bolded "overridden function" there. If you are using the override keyword, you know there is a base method; this is statically guaranteed!

If it isn't overridden, of course, there is no parent to fallback on, so in(false) there would always fail! So don't use this pattern without override so you get that guarantee that there actually is something to inherit.

Contracts also work on interfaces, and so does the override keyword, so use them both there! The implementation class for an interface is a great place to use this. See the examples section.

BTW I didn't realize this until today, but the body or the do keyword on function bodies are unnecessary using the newer-style short contract syntax! Like I did in my examples here, I think it looks pretty OK.

I wrote a little bit about output ranges on stack overflow this week too:


1 import std.stdio;
2 import core.exception;
4 interface I {
5 	void test(int a)
6 		in(a > 20);
7 }
9 class Implementation : I {
10 	override void test(int a)
11 		// without this pattern, the interface
12 		// contract gets ignored! So we want to
13 		// specify we will use it, without repeating
14 		// it... which our new pattern allows:
15 		in(false) // use interface's contract
16 	{
17 		writeln("implementation called");
18 	}
19 }
21 void main() {
22 	try {
23 		auto d = new Implementation();
24 		d.test(30); // OK by interface contract
25 		d.test(10); // and now this throws too
26 	} catch(AssertError ae) {
27 		writeln("correctly thrown with interface!");
28 	}
29 }