Well, I could flip the question: is there any research which show that the small bit of extra hassle involved reduces productivity in a measurable way, or increases the bug count? Note that it's generally quite easy to make something mut
if you find out you need it: it is a single simple error fix in an IDE, and it's also simple to do manually. But I doubt that any conclusive research exists which solves this question one way or another, for the reasons @kornel mentioned.
But anecdotally, yes, it occasionally catches a bug for me. I try to avoid mut
whenever possible, so if I get an unused mut
lint warning, it usually means that I forgot to mutate the variable (which could happen e.g. because I have created a new binding, or mutated a shadowing binding in inner scope, instead of mutating the original variable).
Rust provides ample tools to avoid using mutable state (e.g. the collect::<C>()
method, iterators, Option
combinators etc), so it is generally a non-issue that you have immutability by default. Even in the languages without that kind of support, like Python, I have long learnt to use the Static Single Assignment style of coding, where a variable is never reassigned or shadowed once it's declared. In my experience, it reduced the bug count, because unexpected mutations are damn easy to make and damn hard to find.
That is a commonly repeated misconception (note that the post you cite is from 2014 and is quite outdated). Immutability is part of Rust's memory model, just like aliasing and ownership. It is unconditionally UB to mutate through a &T
(unless the mutated memory is inside of UnsafeCell
), regardless of its uniqueness. It is similarly unconditionally UB to mutate an immutable local binding. However, that restriction follows from the rule above and the type system, since you can't call ptr::addr_of_mut!
on an immutable binding.
Yes, you can always side-step the immutability of a binding by moving it into a new mutable binding, but as far as the language is concerned, this is an entirely different entity.
The difference between mutability and uniqueness is also present in the &uniq
unique immutable references used in closures, mentioned in the post. While Niko argued to remove them entirely, they are a part of the current language, so need to be accounted for in any model.
From the user's perspective, it's the mutability that is important and which I want to get, and exclusiveness is just something to endure, because it prevents UB and bugs. I struggle to think where I could want exclusive references, and mutability would be something tagged along which I would have to deal with.
The usual retort is "but UnsafeCell
allows shared mutability, so it's sharing which is the real foundation of the language". That's not correct. UnsafeCell
can be used to implement shared mutability, but it's more complex than that. UnsafeCell
is, basically, "I have this thing by value, but have none of the usual guarantees about it". As people come to realize, even being initialized is not something which can be unconditionally guaranteed under an UnsafeCell
(otherwise you get unsoundness). As you know, UnsafeCell
also inhibits niches within the contained type, which would lead to unsoundness otherwise.
Finally, even non-aliasing of &mut T
is not a given if T: !Unpin
. This looks like a hack to me, but that's the world we currently live in. In general, it's certainly not unconditionally unsound, even if dangerous, to have shared mutability in a single thread.
Rust's type system and memory model are way more complex and with more exceptions than a simple slogan can describe, whether it's "references can be shared and exclusive" or "references can be mutable or immutable". "&mut
is about non-aliasing" is a useful change of perspective for new rustaceans, and it does make it easier to understand RefCell
or File
(which allows file mutation through a &
). But at the end of the day, neither of those slogans paints a complete or non-contradictory picture, so I see as counterproductive the emphasis some people put on them.