Declaring a single const: enum vs const vs immutable

H. S. Teoh hsteoh at qfbox.info
Wed Sep 10 17:33:48 UTC 2025


On Wed, Sep 10, 2025 at 04:52:51PM +0000, Brother Bill via Digitalmars-d-learn wrote:
> Is there any reason to pick one of these vs. another one, or are they
> all equivalent?
[...]

They are most definitely not equivalent.

`enum` defines a compile-time constant.  It occupies no space, and its
value is "copied" into every expression in which it appears.  For
example:

```d
enum x = [ 1, 2, 3 ];
auto myArray = x;	// equivalent to `auto myArray = [ 1, 2, 3 ];`
auto arr2 = x;		// a distinct copy of [ 1, 2, 3 ] is made
assert(myArray !is arr2);
```

Note that each time x is referenced, a new copy of its value is
generated.

`const` and `immutable` are type qualifiers, and declaring a constant
with them creates a variable in memory containing that value. For
example:

```d
immutable y = [ 1, 2, 3 ];
auto myArray = y;	// creates a reference to y
auto arr2 = y;		// creates another reference to y
assert(myArray is y);
assert(myArray is arr2);
```

Note that y's contents are *not* duplicated when you assign it to other
variables.  Instead, it is copied by reference, and changes to the data
via the copied reference will show up in the original (though in this
case, it's illegal to modify immutable).

//

Now, what's the difference between `const` and `immutable`?  The
following diagram, if you'll excuse the ASCII art, represents an
"inheritance diagram" of the 3 qualifiers, with "mutable" representing
no qualifer at all:

		const
	       /     \
	mutable       immutable

Both mutable and immutable implicitly convert to const, but const does
not implicitly convert to either.

Mutable (i.e., no qualifier) means that the variable may be freely
modified.  Immutable means that it cannot be modified by anybody, not
this code, nor any other code anywhere else in the program.  Const means
that it may be not be modified by this code, but *may* be modified by
some other code elsewhere.

The implicit conversion to const is justified, because not being allowed
to write to a mutable variable does not break any guarantees of mutable
(there are none), and not being allowed to write to immutable is
required because nobody may write to immutable.  The code holding the
const reference may not know whether the data is mutable or immutable,
but it doesn't matter because it can't write to it anyway.

It's invalid to implicitly convert const to immutable, because the code
holding the const reference does not know (cannot guarantee) that
somebody else doesn't hold a mutable reference to the data.

For declaring a single constant, there's not much difference between
const and immutable, of course.  But the distinction becomes important
when you start working with complex types.  Unlike C/C++, const in D is
enforced by the compiler (unless you override it with a cast, but that's
@system and not allowed in @safe code), and is infectious (you cannot
modify data through a const reference even if the data itself is
mutable).

//

A good example of the interaction between const/immutable/mutable is
strings.  D defines `string` as `immutable(char)[]`, i.e., a mutable
reference to an immutable array of characters.  The reference is mutable
(you can take substrings, reassign it to point to a different string,
etc.) but the characters themselves are immutable -- nobody can modify
them. This allows the char data to be stored in read-only memory, and
freely shared between threads without worrying about race conditions.

However, sometimes you may wish to work with an array of mutable chars,
e.g., if you're constructing a complex string and need to modify
individual chars in the process.  So you'd work with `char[]` instead of
`string` (i.e., `immutable(char)[]`).

But then there will be code that doesn't care either way, and that could
work with either string or char[].  They don't modify any data, so it's
wasteful to use a template function for them (it would generate complete
identical assembly code, causing needless redundancy in your
executable).  What can we do in this case?  Const comes to the rescue!
By having said code receive `const(char)[]` as argument, it can work
with both string (immutable) and char[] alike with no code duplication.
Since the code doesn't modify the data, it doesn't break guarantees of
`immutable`, but it doesn't require immutable so it can also work with
mutable data.

In short:

```d
auto myFunc(string s) {
	// s cannot be modified by anybody, not this function, not
	// anybody else. It's safe to expect s not to change in the
	// future.
}

auto myFunc2(char[] s) {
	// s can be freely modified, by this code and by other code.
	// There's no guarantee s won't change later.
}

auto myFunc3(const(char)[] s) {
	// This code can work with both string and char[].
	// It cannot modify s, but somebody else might, so there's also
	// no guarantee s won't change later.
}
```

Finally, recommendations:

- If your constant value is a POD, generally it's a good idea to declare
  it as `enum`, which creates a compile-time constant. Then it can be
  used for compile-time constructs that require its value to be known at
  compile-time.

- If your constant value is an array or other non-POD, you should
  probably declare it as immutable. This ensures no redundant copies of
  it are made, and the data can be put in the program's read-only
  segment, which can protect it from intrusive modification (e.g. by
  malware / hacking attempts).

- `const` is probably OK for declaring POD variables if you're too lazy
  to type `immutable`.  But generally not a useful distinction from
  immutable.


T

-- 
I have no Monet for Degas to make the Van Gogh, because I'm Baroque.


More information about the Digitalmars-d-learn mailing list