So I’ve gone back and forth on this. In particular, I was considering a proposal where we:
- Allow fields to be declared as mutable. A field declared as mutable can be mutated through an
&uniq reference, but a field not declared as mutable cannot be.
- Introduce
&uniq.
What makes this interesting is that
(a) &uniq T could be covariant with respect to T, unlike &mut T, which must be invariant. This is sort of type-theory jargon of course but it basically means that with &uniq T, lifetimes in T can be approximated. When you start building up more complex programs that employ lifetimes, not being able to approximate lifetimes starts to become a burden at times.
(b) Given an &uniq T, fields that are not declared as mut can be safely shared. That is, I could do &uniq_ref.non_mut_field. This potentially addresses a problem that sometimes arises where you have a struct that has some mutable fields and some non-mutable fields, and you’d like to keep references into the non-mutable fields while you call &mut self methods that will modify the mutable ones.
However, I realized later that point (a) doesn’t quite work as well as I thought, and in fact our variance story gets somewhat worse. In particular, if you declared fields as mut, then any type/lifetime parameters reached through those fields would have to be invariant, so that &uniq is sound. This works against our current story where, if you avoid interior mutability, you get this nice “variant when shared, invariant when being mutated” behavior that is exactly what you want.
Similarly, while it’s true that (b) would be helped somewhat, the better solution is to refactor your types so that the mutable data is grouped into a distinct type of its own (and the methods in question are moved there). Putting everything into one type, with some fields declared mut, is not particularly helpful for parallelism, because you still could not pass a &uniq self to multiple threads. In other words, Rust currently encourages you to refactor in ways that make the code better compartmentalized, which in turn is better for parallelism, which seems good overall.
So I guess my conclusion was that &uniq combined with mut fields is a promising idea, but doesn’t carry its own weight. Adding another kind of reference would make the language feel a lot more complex, I think, and introduce a lot of annoying decisions (should I make this method &mut self or &uniq self?), and we should not do that sort of thing lightly.
Sorry this message is so dense! I wanted to get my thoughts on this topic down somewhere for future reference, but I don’t have time to dive into a lot of examples etc just now.