Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D: Const correctness - what am I doing wrong?

I am currently implementing a binary-tree-based data structure. As part of that, I have (currently public, for easier testing) instance variables left and right as part of each Node object used in creating my structure. One of the things I want to be able to access quickly is a sub, which is done using this function:

@property Node sub() 
    in {
        assert(!isLeaf);
    }
    body {
        return (val != left.val) ? left : right;
    }

All of the stuff this refers to is public. Now, I tried to use this property function in a contract (specifically the out block, with the result of the function binding to result). However, when I did this, the compiler complained that I'm calling a mutable method using the const result object. When I changed the signature of sub to be @property const Node sub(), however, I get this compiler error instead:

Error: cannot implicitly convert expression (this.val != this.left.val ? this.left : this.right) of type const(Node) to tournament2.Node

What am I missing here? How can I resolve this?

like image 789
Koz Ross Avatar asked Feb 11 '14 05:02

Koz Ross


1 Answers

The initial problem stems from the restriction that contracts cannot modify the object they belong to (otherwise, the program might behave differently in debug and release mode). The way the language enforces that restriction is by making the this pointer const.

A const this means is that you can't modify the this object's fields, and as for calling methods - those methods themselves must be annotated as const, and the same restrictions apply to the code of those methods. This explains the first error: the contract, which was const, was trying to call a non-const (mutable) method. Since mutable methods are allowed to modify this, and contracts are forbidden to do so, the compiler forbids the call.

Because of the transitive nature of D's constness, everything that's accessed through the this pointer becomes const. And, if any of the class fields are reference types, the target of their indirections becomes const too. This is how the type system will forbid modifying anything that can be reached through the this pointer.

The implication of this is that if a const method is trying to return a class field (with indirections, such as a class type like Node), the returned type must also be const. This is the source of the second error message: the return expression attempted to convert the const Node value, obtained through the const this, to a mutable Node. Currently, the syntax @property const Node sub() indicates that the method itself is const (and has as const this), not the return type.

Now, generally, in C++, an object with proper constness support will usually have multiple overloads for the methods which return a class field: const, and non-const. The const versions will return a const reference; the non-const will return a non-const reference. The two versions are needed to allow obtaining a mutable field reference when we have access to a mutable object, but still allow getting a const reference if we only have const access to the object. Most of the time, the code of the two methods will be identical - only the function signature will differ.

This is where D's inout comes in. Specifying it on the method and some of its arguments or return value means that those arguments or return value will have the same constness as that of this (the object being referred to). This avoids code duplication for the simple cases where returning const and mutable values uses the same code.

The syntax I used is @property inout(Node) sub() inout. I think it can be written in more than one way, but this syntax is unambiguous. Here, the parens in inout(Node) make it explicit that we apply the attribute on the return value, and not the function, and placing the method (this) inout attribute after the parameter list, like const in C++, unambigously specifies that it applies to the function itself.

like image 119
Vladimir Panteleev Avatar answered Sep 21 '22 15:09

Vladimir Panteleev