Could you elaborate or link to some material / discussion regarding that? I'd be very interested in how the alternative approach you're describing would change (or make unnecessary) constructs like RefCell.
The thing I’m noting about atomics, RefCell and Mutex is how they have methods that (safely) mutate themselves, despite taking &self, a supposedly immutable reference.
(I’ve modified the final sentence in my original comment to clarify this.)
let mut x = 1;
let y = &mut x;
let z = &mut *y;
*z += 1;
*y += 1;
assert_eq!(x, 3);
z and y both point to x, but only one is accessible at any point in time. Touching y finishes the z borrow; if you swapped the increment lines, it wouldn’t compile.My memory is fuzzy (this was quite some years back), but I have a vague feeling that this lack of precision in the use of the word “unique” was a factor in some baulking at the proposed change. (“You’re trying to fix something that we admit is strictly wrong, but you’re not even making it right!”)
let mut x = 1;
{
let y = &mut x;
{
let z = &mut *y;
*z += 1;
}
*y += 1;
}
assert_eq!(x, 3);So here's a funny thing: depending on what you mean, I don't think this is actually true. Let me explain.
The shortest way of explaining lexical lifetimes vs NLL is "NLL is based on a control-flow graph, lexical lifetimes are based on lexical scope." CFGs feel more complex, and the implementation certainly is. So a lot of people position this as "NLL is harder to understand."
But that assumes programmers think in the simple way. I think one of Rust's under-appreciated contributions is that programmers intuitively understand control flow graphs better than we may think, and may not intuitively understand lexical scope. Sure, by some metric, NLL may be "more complex" but practically, people only report it being easier to learn and do what they would naturally expect.
Perhaps it's all just confirmation bias on my end, but I think truly understanding lifetimes was easier the way I learned it back then. That said, I have never bothered to write documentation for the Rust project, whereas few Rust contributors can claim to be in the same league as you in that area. We probably have very different perspectives.
So, I think this is the thing: I also think that it was easier to learn lexically, personally. I too was worried that it would make things harder. But, I just don't think that's been demonstrated to be true across most people. It is, however, only my gut-check feeling for what I've seen. I could be wrong.
(And, the borrowcheck documentation is very minimal, and didn't really change with NLL, other than needing to add some println!s to make things not compile again. So it's certainly not because I wrote some amazing docs and explained it in a good way, hehe.)
EDIT: i think some tooling showing lifetimes for borrows would be very helpful. Can mir do this? I haven't tried it.
Does it really fall apart? None of these constructs change the fact that if a &mut T is available, you can call functions on that type that take &mut self. The only thing that breaks is the assumption that non-presence of mut implies that there is no way to call those &mut self functions. If you change it to "non-prsence of mut implies that you need extra constructs like atomics, RefCell or Mutexes in order to get mutable access" it works out again.
This becomes a huge deal when people think that they have an immutable data structure just because they have an & reference to it. Someone will inevitably stash a mutex or similar in there because they just need to be able to mutate it this once… I mean these three times… I mean all the time. Oh, you wanted this immutable data structure so you could diff it for your VDOM or incremental calculations or whatever? Heh, guess you’ll have to find some other way or structure things so that mutations can be propagated through the object tree or something. And you’ll keep on having cache issues from time to time when people forget to jump through the right hoops. Sorry about that.
So yeah, the mental model of &/&mut references being about mutability is just completely and harmfully wrong. Rust does not have a way of guaranteeing immutability.
&mut still implies mutability, and even the constructs like Mutex or RefCell still expose their mutability support via &mut references. Only atomics don't (they don't provide any &mut access). Note that you don't add const like in C/C++, you add &mut.
As for the VDOM caching thing, you have a point. But even if Rust had &uniq and &shared pointers, the challenge would be the same. Users might still use constructs like RefCell.
Sure, both do have get_mut() which takes &mut self (though these came after 1.0), but that’s seldom how you’ll actually access it.
> But even if Rust had &uniq and &shared pointers, the challenge would be the same.
Remember again that the mutpocalypse wasn’t ever about changing the semantics, just about fixing incorrect labelling. I’m not saying the challenges would be different, just that &mut is false advertising, giving people the impression that & means an immutable reference, when it just doesn’t. &/&mut misleads people into thinking they can have immutable data structures, but you’d actually need an OIBIT (like Send) to achieve that.
Yeah they take &self, but that wasn't my point. My point was what they expose, which is MutexGuard in the case of Mutex, and if you want to modify the interior, you'll likely use the DerefMut impl which returns a &mut T. Same goes for RefCell::borrow_mut().
My point was that you don't mutate a &self reference directly in these instances, but turn a &self reference into a &mut self reference and then mutate. Again, atomics are an exception.
> just that &mut is false advertising
it is not false advertising, it implies mutable access. Had Rust been mut by default and had &T been &const T instead, I would agree with the false advertisement point. But thankfully it's non-mut by default.
Anyways, even if &mut were false advertising, and it were a bump in the learning curve, note that &uniq is such a bump too, because you have no idea what it says or implies initially. &mut makes it clear that if you want to mutate, you'll likely need a &mut somewher. But &uniq does not make this obvious, so you are required to learn something early on in Rust's comprehension, while &mut requires you to learn something later on. Generally, Rust has been criticized for having a steep learning curve at the start. In general I think it's worth it to learn Rust, but this wouldn't make it any better but worse.
RefCell allows you to get a mutable reference via an allegedly-immutable reference. That it goes via a guard type is quite immaterial. It breaks the model.
&mut implies & is immutable.
Rust is teaching an incorrect mental model, which I believe is the wrong mental model to teach. Sure, &uniq is not devoid of problems, because most of the time &mut will suggest the right thing for beginners. But the spelling &mut is categorically wrong at the fundamental technical level. It was never actually about mutability.
In the end, social factors and inertia trumped technical precision and consistency. I am strongly inclined to think this was a mistake. Mutability is easier to explain initially, but teaches an incorrect mental model that hinders a correct understanding of Rust’s approach to memory safety, falling apart as soon as you touch things like atomics, RefCell or Mutex, which mutate self despite taking it as &self, a supposedly immutable reference.