In the context of class and struct instances, it's easier to understand the relationship const
and immutable
have with data. Here's a simple example:
struct ModMe { int x; } void main() { immutable ModMe mm; immutable(ModMe)* pmm; mm.x = 1; // Error pmm.x = 2; // Error }
The declaration of mm
creates an instance of type immutable(ModMe)
. Not only can no assignments be made to mm
, mm.x
cannot be modified. pmm
is a pointer to immutable data, so the pointer can be reassigned, but pmm.x
cannot be modified. Now look at this:
struct ModMeHolder { ModMe mm; } void main() { immutable ModMeHolder mmh; mmh.mm.x = 1; }
As immutable
is transitive, applying it to mmh
causes mm
to also be immutable. If, in turn, it had any class
or struct
instance members, they would also become immutable. As they say, it's turtles all the way down. The same is true for const
, but recall that its contract is not as strict as that of immutable
:
ModMe mm; const(ModMe)* pmm = &mm; mm.x = 10; writeln(pmm.x);
Attempting to modify pmm.x
would be an error, since pmm
is a pointer to const
data, but it's perfectly legal to modify the original data behind pmm
's back.
Changing the previous ModMeHolder
example to use const
instead of immutable
will not change the outcome. This sometimes causes consternation for C++ programmers experimenting with D. Imagine you want to implement a class that increments an internal counter every time a function is called. Pass an instance of that class to a function that attempts to call its incrementing member function through a const
reference and a compiler error will result. In C++, programmers can get around this by declaring the counter as mutable
. Not so in D.
D const
is strictly physical const. This can be seen as a guarantee that not a single bit of an instance can ever be modified through a const
reference, internally or externally. The alternative is logical const
. With this definition, a reference appears const
to the outside world, but the instance is able to modify its internal structure. While this may seem quite useful to the programmer, it completely breaks any guarantee of transitivity. If the compiler cannot guarantee transitivity, then any assumptions it makes about the state of an instance are invalid. This could have wide-ranging consequences. For example, it could be a source of race conditions in a multi-threaded program. The short of it is, in order to guarantee transitivity, logical const does not exist in D as I write. Who can say, though, what the future holds.
const
and immutable
can be applied to class
and struct
declarations, but any instances of such types must still be declared as const
or immutable
.
We've only seen
const
used as a type qualifier thus far, but when applied to a function it is a storage class. As in C++, this is used to allow member functions to be called from a const
or immutable
reference:
class CallMe { void foo() {} void bar() const {} } void main() { const CallMe cm = new CallMe; cm.foo(); // Error – mutable function on const reference cm.bar(); // OK }
const
can be applied to the front of the function declaration as well as at the end. This has implications for how const
return values are declared:
const AStruct returnSomething() {}
It's easy to assume this is returning a constant struct instance, but that is not the case. This is a function with a const
storage class. For clarity, it's good practice to apply const
at the end of a function declaration when needed and to declare const
return values using the type qualifier syntax, const(AStruct)
.
3.15.189.199