Added copy constructors to "Programming in D"
H. S. Teoh
hsteoh at quickfur.ath.cx
Wed Feb 9 19:25:26 UTC 2022
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.
More information about the Digitalmars-d-announce
mailing list