D from the ground up

immutable, const and enum

Three ways to promise that a value will not change, what each promise costs, and why the compiler takes them more seriously than a comment.

Part 3 of 12

Beginner13 min readBack to Courses

immutable means nobody can change it

An immutable value is fixed at the moment it is created and never changes afterwards, anywhere in the program. immutable string Brand = "zoom"; declares one, and any later attempt to assign to Brand is a compile error rather than a bug that appears at three in the morning.

Because the value can never change, it is safe to share: two threads reading the same immutable data cannot interfere, and no copy is needed to protect it. That is the reason a D string is immutable — text passed between functions cannot be quietly rewritten by the function that received it.

const is a read-only view

const says that this name will not write to the data. The data itself may belong to someone else and may change through another name. A parameter written const int[] xs promises that the function only reads the array, and a promise like that lets the same function accept both mutable and immutable arguments.

The difference is worth holding on to: immutable is a fact about the data, const is a rule about one name. A function that takes const is telling every caller that the array will come back exactly as it went in.

A const parameter can accept an immutable argument, because a promise not to write is weaker than a guarantee that nobody can. The reverse is not true: an immutable parameter cannot take a mutable array, since the caller could change it afterwards. That one-way rule is why library functions usually ask for const.

enum is a constant with no storage

enum Wheels = 4; declares a manifest constant. Every use is replaced by 4 at compile time, so there is no variable, no memory, and no way to take its address. A manifest constant can be used where a value known at compile time is required, such as a case label in a switch.

Reach for enum for a number that is part of the definition of the program, immutable for a value computed once at startup, and const on parameters that should not be written. Three promises, and each one removes a class of mistake.

An enum can also name a value computed from other constants, as in enum Area = 3 * 4;. The compiler evaluates the expression, so the result is a number in the instruction rather than a load from memory. A static immutable computes the value once and keeps it in storage, which suits a value that is expensive to build.

The promise reaches inside

immutable and const are transitive: they apply to the value and to everything reachable from it. An immutable int[] is an array whose elements cannot change, and the compiler enforces that through every slice taken from it. A const view of a struct makes every field read-only at once, not only the field you happened to name.

That depth is what makes the promise useful. A function taking const int[] cannot keep the array and change it later, because it has no mutable name for it. The caller keeps the right to modify the data through its own name; the callee simply agrees not to.

When the value is known

An enum value exists only while the program is compiled, so it can stand in a case label, an array length or a template argument. A module-level immutable is initialized once before main runs and occupies storage; a local immutable is fixed when execution reaches its declaration.

Choose by the question you are answering. Is this a fact about the program that never needs memory? enum. Is it a value computed once and shared? immutable. Is it a parameter that must not be written? const. Three promises, and each one removes a class of mistake.

A constant, an immutable value and one read

import std.stdio;

enum Max = 3;
immutable string Unit = "kg";

void main() {
    immutable int weight = 4;
    writeln(Unit, " ", weight, " of ", Max);
}

A read-only parameter and an immutable array

Run it, then add a line that appends 2 to seats and read the compiler error before removing the line. Then change total to take int[] instead of const int[] and see which call stops compiling.

Output
Click "Run" to execute your code

An immutable array cannot grow: appending to it with ~= is a compile error, not a silent copy. Leave a collection mutable until its contents are final, then make the finished value immutable.

A read-only view of data that moves

Run it and compare the two views of `xs`. Then change `xs[0]` before the first print and explain the result. Finally try `fixed[0] = 0;` and read the error before removing the line.

Output
Click "Run" to execute your code

Three mistakes: writing immutable int limit; and assigning later, which is a compile error because an immutable value must be initialized where it is declared; expecting a const parameter to stop the data changing elsewhere, when it only stops this function writing; and using a const variable as a case label, which needs the compile-time value an enum provides.

Try these next

  • Change xs[0] before the first print and explain why the const view changes with it.
  • Add a mutable copy of fixed with .dup, modify the copy, and confirm the immutable original is untouched.
  • Move enum Max inside main and check that the case label still compiles.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What is the difference between immutable and const?