The dangers of mutation specifically apply to alias mutation; a lot of times when people claim "immutability", what they really claim (or want) is alias immutability.
An alias is a value that is referenced by multiple variables. For example in C:
int x = 4;
int* y = &x;
x
and y
are aliases of each other. If one of them is mutated, the other changes.
x = 5;
printf("%d", *y); // prints 5
*y = 3
printf("%d", x); // prints 3
The problem with alias mutation is that it's hard (sometimes literally impossible) to know whether a variable's alias was mutated. For example:
int* y;
int main() {
int x = 4;
y = &x;
f();
printf("%d", x);
return 0;
}
What will this print? To know the answer, we need to know the body of f
, because it could re-assign *y
. Moreover, if f
is:
void f() {
g();
h();
i();
}
Now we need to know the body of g
, h
, i
...
Alias mutation has real-world consequences. Every day, a developer has a variable that they assume isn't going to mutate, but it was aliased somewhere, and some function mutates that alias, so it mutates the variable. The developer can't keep track of every alias of every variable, so they can't know at a glance "is this call to f
going to change x
" for some f
s and x
s; they assume, and when these assumptions turn out to be wrong, it's a pain to debug.
But mutation without aliases doesn't have this issue. If the only way x
can be mutated is through a statement x =
, a developer can be certain that it remains constant throughout some lines, by checking that none of the lines contain x =
. In fact, even if the language has a "mutable borrow" that temporarily lets other functions mutate a value, e.g. f(&mut x)
lets that particular call to f
mutate x
, a developer can still be certain that x
remains constant throughout every line that doesn't contain an assignment or mutable borrow of x
.
And this basically the generic pitch for Rust, a language that permits mutability but exclusively forbids alias mutability (except for explicitly-allowed "interior mutability"). But even outside of Rust, understanding the unique danger of alias mutation is useful. For example, if you're careful not to leak variables, i.e. assign them globally or pass them to functions except behind "read-only" wrappers, you can be certain that they aren't mutated within a particular code region even in C++ and Java.
x = 10
is more likeint *x; (*x) = 10
in C++.x
is just a name for an object, not the chunk of memory containing the object itself.