Thread overview
Declaring a single const: enum vs const vs immutable
2 days ago
Brother Bill
2 days ago
monkyyy
2 days ago
H. S. Teoh
2 days ago
H. S. Teoh
1 day ago
IchorDev
2 days ago

Is there any reason to pick one of these vs. another one, or are they all equivalent?

If equivalent, it would seem that immutable appears to be the 'strongest', whereas enum has fewer keystrokes.

Is there a D 'best practice' for this?

	const int foo1 = 42;
	enum foo2 = 42;
	immutable int foo3 = 42;

	const string str1 = "Any difference";
	enum str2 = "Any difference";
	immutable string str3 = "Any difference";

2 days ago

On Wednesday, 10 September 2025 at 16:52:51 UTC, Brother Bill wrote:

>

Is there any reason to pick one of these vs. another one, or are they all equivalent?

If equivalent, it would seem that immutable appears to be the 'strongest', whereas enum has fewer keystrokes.

Is there a D 'best practice' for this?

	const int foo1 = 42;
	enum foo2 = 42;
	immutable int foo3 = 42;

	const string str1 = "Any difference";
	enum str2 = "Any difference";
	immutable string str3 = "Any difference";

I just do always enum; going all in on compile time abstractions

2 days ago
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.
2 days ago
On Wed, Sep 10, 2025 at 05:15:16PM +0000, monkyyy via Digitalmars-d-learn wrote: [...]
> I just do always enum; going all in on compile time abstractions

Be careful, this may not always be what you want. For example:

```d
enum data = [ 1, 2, 3 ];

void main() {
	auto buffer = data;	// GC allocation
	...
	auto tmpBuf = data;	// another GC allocation
	...
	assert(buffer is tmpBuf); // fails
}
```

Whereas:

```d
static immutable data = [ 1, 2, 3 ];

void main() {
	auto buffer = data;	// no GC allocation
	...
	auto tmpBuf = data;	// no GC allocation
	...
	assert(buffer is tmpBuf); // true
}
```


T

-- 
Never wrestle a pig. You both get covered in mud, and the pig likes it.
1 day ago

On Wednesday, 10 September 2025 at 17:33:48 UTC, H. S. Teoh wrote:

>

enum defines a compile-time constant. It occupies no space, and its value is "copied" into every expression in which it appears.
[...]
const and immutable are type qualifiers, and declaring a constant with them creates a variable in memory containing that value.

Well said. It's an important and easy-to-misunderstand part of the language, and you explained it very well here.

>

const in D is [...] infectious (you cannot modify data through a const reference even if the data itself is mutable).

I'd like to clarify this a little:
D's type qualifiers (const, immutable, shared, and inout) are usually described as being 'transitive'. What that means is that anything inside a const object is also const, and anything inside a shared object is also shared, etc.
So if a struct instance is const, then its fields are const; if an array is const, then its elements are const; and so on.

>

D defines string as immutable(char)[], i.e., a mutable reference to an immutable array of characters.

Bonus fact: string is not a keyword in D! (a fact that I feel compelled to point out because I've seen SO many people assume that it's a keyword!)

>

Finally, recommendations:
[...]

Very good advice also.