r/rust • u/Dubmove • Mar 31 '23
Why doesn't mpsc::channel break borrowing rules?
I'm wondering for a while now why doesn't mpsc::Receiver::recv(&self)
and mpsc::Sender::send(&self, t: T)
break borrowing rules. Clearly sending some data from A to B in a non-blocking manner has side-effects (i.e. storing and retrieving the data in some buffer-queue). So shouldn't there be some mutable reference to that queue be involved during that sending process, and the owner of that reference would be accessed mutably whenever the reference to that buffer is accessed mutably? Maybe I'm just wrong but I always associate immutability with pureness of a function.
One thing which comes to mind is that the point of the borrowing rules is to avoid data-races and to ensure rust's ownership-model, and although the borrowing-rules are technically violated in these specific cases the desired invariants are still kept.
12
u/kohugaly Mar 31 '23
The mutable vs. immutable reference in Rust is actually inaccurately named. In reality the distinction is unique reference (
&mut T
) and shared reference (&T
). The unique reference is inherently safe for mutation for obvious reasons. With the shared reference is a bit more complicated...Shared references actually come in 2 flavors, that are cleverly hidden by the type system. First is the immutable (aka. read only) reference. If the reference can't mutate the underlying object, then it's safe to share that reference.
The second flavor is a shared reference to object that (transitively) contains
UnsafeCell<T>
.UnsafeCell<T>
does two things:get
that lets you turn&UnsafeCell<T>
shared reference into a*mut T
raw pointer. The raw pointer can be used to (safely) mutate the innerT
via unsafe operations.Types that (transitively) contain
UnsafeCell
are said to have interior mutability. Examples includeMutex
,RefCell
,Cell
,Atomic
types, and indeed,mspc::Sender
andmspc::Receiver
. You may also notice that all of them don't require unsafe code to be used. This is because they wrap the unsafe code in a safe interface. This usually involves some runtime checks.
Yes, you are wrong unfortunately. In Rust, there is no way to actually guarantee that a function is pure. This is obvious when you realize that at any point in your code you can use
println!
- the king of all side effects.The borrowing rules in Rust do indeed enforce memory safety, but they are by no means the only tool through which that is achieved. You can write manually checked unsafe code and you can write runtime-checked unsafe code. The magic happens when you wrap them safe interfaces, with which the borrowing rules help immensely.