It's been mentioned in passing a couple of times, but I want to emphasize another reason for &i32
to disallow mutation just like &Vec<i32>
, even though it's only an integer.
The reason is optimization, and particularly function-local optimization that does not require full-program knowledge (which may or may not even exist, in dynamic cases). When you pass a &i32
or a &mut i32
to a function, Rust can optimize the way that function uses that reference, reducing and reordering the loads and stores in the program.
For example, in your original obj.bla()
example, the closure receives parameter it: &mut i32
, derived from a &mut Vec<i32>
. But, as a hidden field of the closure object, there is also at the same time an &Obj
, from which the closure could derive a &Vec<i32>
or &i32
, and which it could store somewhere between iterations.
Without making your example an error, or without looking at the definition of iter_mut
or for_each
or the body of the closure, we cannot know whether they will, say, reallocate the Vec
with that &mut Vec<i32>
, which could invalidate any &i32
s the closure might have created.
But because your example is an error, not only is that sort of pointer invalidation guaranteed not to happen, but we can optimize based on that assumption. Anyone who does happen to hold a &i32
or &mut i32
can cache things it reads, or skip redundant writes, etc, even if those reads/writes happen across completely unknown, side-effecting, global-state-using, dynamic-dispatch function calls.
C++ cannot perform these optimizations, because its const int *
type does not have this requirement. What Rust has is more like C's restrict
pointers, but checked for correctness at compile time.
This is why we have Cell
and RefCell
and Mutex
and so forth- &i32
's immutability is a huge optimization enabler, but when what you want to do would make those optimizations incorrect, you just need some way to turn them off. That's what those types are for- not to make your life miserable because you aren't using the default types, but to let Rust more heavily optimize the default types.