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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With