Let's say we have declared an immutable (not let mut
) RwLock
instance like:
let value = RwLock::new(0);
Because the value
is immutable, I expected that I can not change the inner value of the RwLock
. However when I tested, apparently this works:
{
*value.write().unwrap() = 5;
}
I am wondering if I used RwLock
in a wrong way, that this should not have happened (I am worried that the lock might not actually function as expected, if this is the case). However, I am sure there's some explanation behind this behavior since Rust is very explicit when it comes to what you can change and what not.
My guess is the RwLock
stores its inner value on the heap so it only needs to keep track of an immutable pointer to the value. So, whenever we write to the value the RwLock
struct itself would be kept untouched since the pointer wouldn't change.
This is just a guess, and probably a wrong one. I am more than happy if someone would like to correct me.
For clarification: I know how Reader-Writer locks are supposed to work. My question is not about the synchronization mechanism, but rather why doesn't Rust treat RwLock
values like any other value when it comes to immutability. Like is it one of the "magic" types that compiler treats differently, or there's something else I am not aware of.
Because you are not mutating the RwLock
, as far as the compiler is concerned.
The RwLock::write()
takes a &self
as the receiver and returns a RwLockWriteGuard
for which the DerefMut
-impl is used to do the assignment *value... = 5
. What happens in between those steps is up to the implementation of RwLock
.
Since you are specifically asking not about RwLock
per se, but the presence or non-presence of "magic": There is absolutely no magic involved here with respect to immutable (&
) or mutable (&mut
) references. In fact, there may be a misunderstanding:
While we call these "immutable" or "mutable" (because that's how they are used), those are better thought of as "shared" and "exclusive" references. With an exclusive reference (&mut
), you are always free to mutate the value without causing mutation-while-aliasing; the compiler can guarantee this (special) use case.
Likewise, with shared references (&
), you are free to alias the value as mutation is not possible. The compiler can also guarantee this (special) case.
While this covers a surprisingly large number of situations, with RwLock
this is neither possible nor desirable. The RwLock
uses it's internal locking mechanism (that the compiler can't know about) to determine if mutation should be allowed, and upholds the guarantee that mutation-while-aliasing does not occur. That is why RwLock
can go from a &self
to mutating the inner value.
The same is true for the RefCell
-type, btw.