rustborrow-checkerrwlock

Why does Rust allow writing to an immutable RwLock?


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.


Solution

  • 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.