On Tuesday, 2 February 2016 at 23:41:07 UTC, H. S. Teoh wrote:
You're misunderstanding D's type system. Immutable is not a
"better const", as though const is somehow defective. Perhaps
the following diagram may help to clear things up:
const
/ \
(mutable) immutable
What this means is that both mutable and immutable are
implicitly convertible to const. Or, to put it another way,
const is a kind of "wildcard" that can point to underlying data
that's either mutable or immutable.
Mutable data is, well, mutable -- anybody who can get to it,
can modify it. Immutable means *nobody* can modify it once it's
initialized.
Why const, then? Const is useful for when a function doesn't
care whether the underlying data is mutable or not, because it
doesn't need to change the data. Const provides guarantees to
the caller that the function won't touch the data -- even if
the data is actually mutable in the caller's context. It's a
"no-mutation view" on data that's possibly mutable by a third
party.
Furthermore, since const provides actual guarantees that the
called function isn't going to touch the data, this opens up
optimization opportunities for the compiler. E.g., it can
assume that any part(s) of the data that are held in registers
will remain valid after the function call (provided said
registers aren't touched by the function), so it doesn't need
to issue another load after the function returns. As a
contrived example, say you have code like this:
struct Data { int x; }
int func1(Data* d) { ... }
int func2(const(Data)* d) { ... }
...
void main() {
Data d;
int value1 = d.x*func1(&d) + d.x;
int value2 = d.x*func2(&d) + d.x;
d.x++;
}
When evaluating value1, the compiler may have issued a load for
the first occurrence of d.x, then it calls func1. But since
func1 may modify d.x, the second d.x needs another load in
order to ensure the correct value is used.
When evaluating value2, however, since func2 takes a const
pointer to the Data, the compiler knows the value of d.x cannot
possibly change across that function call, so it can safely
reuse the value of d.x that was previously loaded. It may also
go further and refactor the expression as d.x*(func2(&d) + 1),
because the const guarantees that func2 can't mutate d.x behind
our backs and invalidate the result. Such a refactoring is
invalid with func1, because there is no guarantee that d.x will
have the same value after func1 returns.
Now, the same argument applies if immutable was used in place
of const. However, the last line in main() illustrates why we
need const rather than immutable in this case: we actually
*want* to modify d.x in main(). We just don't want func2 to
touch anything. So we can't use immutable -- since immutable
means *nobody* can touch the data. So, const provides both the
guarantee that func2 won't touch the data, thus allowing the
aforementioned optimization, and also continues to let main()
mutate the data at its leisure.
T
In C90+, const can apply to both values and pointers to those
values. And since pointers are themselves values of a memory
address that is consistent usage. That seems to be the only
meaningful distinction here. Pointing to a const value makes the
pointer mutable but the value immutable. Pointing a const to a
value makes the pointer immutable but the value mutable. etc.
Immutable accomplishes nothing distinct here that I can see,
other than making the use of const confusing. To make a function
not change a value you declare a const input. Because it is the
value declared in the function definition that is a const, not
the value you pass it. "Passing as const" doesn't make any
logical sense. To be honest, it smells like the kind of opaque
cleverness D is ostensibly supposed to obviate.