I am trying to wrap my head around Rust smart pointer architecture (full disclosure: C++ background). Most of the restrictions started to actually make sense, but Rc::get_mut() still seems to be overly restrictive – namely it requires no weak pointers.
I wrote a test program to learn how pointer work, and everything else except Rc::get_mut() works as I expect it: If I have only one strong pointer and a weak pointer (or several weak ones), I can drop the strong one forgetting the stored value and I can unwrap it, getting the stored value (as mutable if desired) and consuming the pointer, in both cases weak pointers will fail to upgrade afterwards. However, weak pointers prevent me from mutating the value.
The code is here https://play.rust-lang.org/?gist=4ba8c7195d4f4a7b4d6a655485490f44&version=stable
The effect of this restriction seems to be “If you can reach a value via weak pointer, then this value is constant”. What I fail to understand is the rationale behind this, what kind of bad scenarios does it prevent?
Any help will be appreciated. Thanks!
The difference is
try_unwrap operates on an owned Rc value, whereas
get_mut just has a mutable borrow of an Rc. So the former is destructive - nothing else can try to use that Rc afterwards and the strong count is decremented (possibly allowing resource reclaim). The latter doesn’t touch the strong count at all (it’s just a borrow), is non destructive, and you can easily imagine 2 different Rcs then getting a mutable access(if they upgrade) to the same value which is an aliasing violation.
Thank you! After reading your answer and checking the source code I got an insight: Weak.upgrade() can check if at least one corresponding Rc still exists (and fail at runtime if not), but it cannot easily check if the one and only corresponding Rc was mutably borrowed! Therefore, failing at runtime to avoid aliasing is not an option, and we must ensure no weak pointers exists before borrowing mutable.
For those stumbling upon this thread, here is an illustration https://play.rust-lang.org/?gist=5c74ab9816364cda28035bc51b08a74c&version=stable – you can have either weak pointer or mutable reference, and you can easily see the reason behind get_mut restrictions
That’s exactly it - there’s no indicator (shared amongst the Rcs and Weaks) for this since the mutable borrow is a “local” (for lack of better term) operation.
It's interesting to note that in theory it is possible to have a similar runtime check if we store "is borrowed mutably" flag. And this is exactly what RefCell does!
Ah, yes, I looked at the code, it revolves around (i) storing borrow count (AND also “borrowed mutably” flag in the same variable), and (ii) handling out wrapper objects that behave as if they were of type they wrap while at the same time internally holding a reference to the RefCell so the wrapper can phone home upon destruction and update borrow count. Both are classic C++ techniques, I dare say, but the implementation in Rust is much more concise and easier to follow. Needless to say, this functionality is an overkill for Rc