const ref
Parameters and const
Member Functions
This chapter is about how parameters and member functions are marked as const
so that they can be used with immutable
variables as well. As we have already covered const
parameters in earlier chapters, some information in this chapter will be a review of some of the features that you already know.
Although the examples in this chapter use only structs, const
member functions apply to classes as well.
immutable
objects
We have already seen that it is not possible to modify immutable
variables:
immutable readingTime = TimeOfDay(15, 0);
readingTime
cannot be modified:
readingTime = TimeOfDay(16, 0); // ← compilation ERROR readingTime.minute += 10; // ← compilation ERROR
The compiler does not allow modifying immutable
objects in any way.
ref
parameters that are not const
We have seen this concept earlier in the Function Parameters chapter. Parameters that are marked as ref
can freely be modified by the function. For that reason, even if the function does not actually modify the parameter, the compiler does not allow passing immutable
objects as that parameter:
/* Although not being modified by the function, 'duration' * is not marked as 'const' */ int totalSeconds(ref Duration duration) { return 60 * duration.minute; } // ... immutable warmUpTime = Duration(3); totalSeconds(warmUpTime); // ← compilation ERROR
The compiler does not allow passing the immutable
warmUpTime
to totalSeconds
because that function does not guarantee that the parameter will not be modified.
const ref
parameters
const ref
means that the parameter is not modified by the function:
int totalSeconds(const ref Duration duration) { return 60 * duration.minute; } // ... immutable warmUpTime = Duration(3); totalSeconds(warmUpTime); // ← now compiles
Such functions can receive immutable
objects as parameters because the immutability of the object is enforced by the compiler:
int totalSeconds(const ref Duration duration) { duration.minute = 7; // ← compilation ERROR // ... }
An alternative to const ref
is in ref
. As we will see in a later chapter, in
means that the parameter is used only as input to the function, disallowing any modification to it.
int totalSeconds(in ref Duration duration) { // ... }
Non-const
member functions
As we have seen with the TimeOfDay.increment
member function, objects can be modified through member functions as well. increment()
modifies the members of the object that it is called on:
struct TimeOfDay { // ... void increment(Duration duration) { minute += duration.minute; hour += minute / 60; minute %= 60; hour %= 24; } // ... } // ... auto start = TimeOfDay(5, 30); start.increment(Duration(30)); // 'start' gets modified
const
member functions
Some member functions do not make any modifications to the object that they are called on. An example of such a function is toString()
:
struct TimeOfDay { // ... string toString() { return format("%02s:%02s", hour, minute); } // ... }
Since the whole purpose of toString()
is to represent the object in string format anyway, it should not modify the object.
The fact that a member function does not modify the object is declared by the const
keyword after the parameter list:
struct TimeOfDay { // ... string toString() const { return format("%02s:%02s", hour, minute); } }
That const
guarantees that the object itself is not going to be modified by the member function. As a consequence, toString()
member function is allowed to be called even on immutable
objects. Otherwise, the struct's toString()
would not be called:
struct TimeOfDay { // ... // Inferior design: Not marked as 'const' string toString() { return format("%02s:%02s", hour, minute); } } // ... immutable start = TimeOfDay(5, 30); writeln(start); // TimeOfDay.toString() is not called!
The output is not the expected 05:30
, indicating that a generic function gets called instead of TimeOfDay.toString
:
immutable(TimeOfDay)(5, 30)
Further, calling toString()
on an immutable
object explicitly would cause a compilation error:
auto s = start.toString(); // ← compilation ERROR
Accordingly, the toString()
functions that we have defined in the previous chapter have all been designed incorrectly; they should have been marked as const
.
Note: The const
keyword can be specified before the definition of the function as well:
// The same as above const string toString() { return format("%02s:%02s", hour, minute); }
Since this version may give the incorrect impression that the const
is a part of the return type, I recommend that you specify it after the parameter list.
inout
member functions
As we have seen in the Function Parameters chapter, inout
transfers the mutability of a parameter to the return type.
Similarly, an inout
member function transfers the mutability of the object to the function's return type:
import std.stdio; struct Container { int[] elements; inout(int)[] firstPart(size_t n) inout { return elements[0 .. n]; } } void main() { { // An immutable container auto container = immutable(Container)([ 1, 2, 3 ]); auto slice = container.firstPart(2); writeln(typeof(slice).stringof); } { // A const container auto container = const(Container)([ 1, 2, 3 ]); auto slice = container.firstPart(2); writeln(typeof(slice).stringof); } { // A mutable container auto container = Container([ 1, 2, 3 ]); auto slice = container.firstPart(2); writeln(typeof(slice).stringof); } }
The three slices that are returned by the three objects of different mutability are consistent with the objects that returned them:
immutable(int)[] const(int)[] int[]
Because it must be called on const
and immutable
objects as well, an inout
member function is compiled as if it were const
.
How to use
- To give the guarantee that a parameter is not modified by the function, mark that parameter as
in
,const
, orconst ref
. - Mark member functions that do not modify the object as
const
:struct TimeOfDay { // ... string toString() const { return format("%02s:%02s", hour, minute); } }
This would make the struct (or class) more useful by removing an unnecessary limitation. The examples in the rest of the book will observe this guideline.