H. S. Teoh
Posted in reply to Ali Çehreli
| On Wed, Feb 09, 2022 at 10:28:15AM -0800, Ali Çehreli via Digitalmars-d-announce wrote: [...]
> - const is a promise
>
> - immutable is a requirement
[...]
Strictly speaking, that's not really an accurate description. :-P A more accurate description would be:
- const: I cannot modify the data (but someone else might).
- immutable: I cannot modify the data, AND nobody else can either.
The best way I've found to understand the relationship between const, immutable, and mutable in D is the following "type inheritance" diagram (analogous to a class inheritance diagram):
const
/ \
(mutable) immutable
Const behaves like the "base class" (well, base type, sortof) that either mutable or immutable can implicitly convert to. Mutable and immutable, however, are mutually incompatible "derived classes" that will not implicitly convert to each other. (Of course, the reality is somewhat more complex than this, but this is a good, simple conceptual starting point to understanding D's type system.)
Const means whoever holds the reference to the data cannot modify it. So it's safe to hand them both mutable and immutable data.
Mutable means you are allowed to modify it, so obviously it's illegal to pass in const or immutable. Passing mutable to const is OK because the recipient cannot modify it, even though the caller himself may (since he holds a mutable reference to it).
Immutable means NOBODY can modify it, not even the caller. I.e., *nobody* holds a mutable reference to the data. So you cannot pass mutable to immutable. Obviously, it's safe to pass immutable to const (the callee cannot modify it anyway, so we're OK). But you cannot pass const to immutable, because, as stated above, a const reference might be pointing to mutable data: even though the holder of the reference cannot himself modify it, it may have come from a mutable reference somewhere else. Allowing it would break the rule that immutable means *nobody* has a mutable reference to the data. So that's not allowed.
IOW, "downcasting" in the above "type hierarchy" is not allowed, in general.
However, there's a special case where mutable does implicitly convert to mutable: this is if the mutable reference is unique, meaning that it's the only reference that exists to that data. In such a case, it's OK to convert that mutable reference to an immutable one, provided the mutable reference immediately goes out of scope. After that point, nobody holds a mutable reference to it anymore, so it fits into the definition of immutable. This happens when a mutable reference is returned from a pure function:
MyData createData() pure {
MyData result; // N.B.: mutable
return result;
// mutable reference goes out of scope
}
// OK: function is pure and reference to data is unique
immutable MyData data = createData();
The `pure` ensures that createData didn't cheat and store a mutable reference to the data in some global variable, so the reference to MyData that it returns is truly unique. So in this case we allow mutable to implicitly convert to immutable.
There's also another situation where immutable is allowed to implicitly convert to mutable: this is when the data is a by-value type containing no indirections. Essentially, we're making a copy of the immutable data, so it doesn't matter if we modify the copy, since we're not actually modifying the original data.
//
Now, w.r.t. the original question of when we should use const vs. immutable:
- For local variables, there's no practical difference between const and
immutable, because by definition the current function holds the only
reference to it, so there can't be any mutable reference to it. I
would just use immutable in this case -- the compiler may be able to
optimize the code better knowing that there can't be any mutable
reference anywhere else (though in theory the compiler should have
already figured this out, since it's a local variable).
- For function parameters, I would always use const over immutable,
unless there was a reason I want to guarantee that nobody else holds a
mutable reference to that argument (e.g., I'm storing the reference in
a data structure that requires the data not to be mutated afterwards).
Using const makes the function usable with both mutable and immutable
arguments, which is more flexible when you don't need to guarantee
that the data will never be changed by anybody.
Some people may prefer `in` instead of const for function parameters:
it's more self-documenting, and if you use -preview=in, it means
`const scope`, which adds an additional check that you don't
accidentally leak reference to parameters past the scope of the
function.
T
--
Windows 95 was a joke, and Windows 98 was the punchline.
|