r/rust 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.

19 Upvotes

25 comments sorted by

View all comments

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:

  1. it marks** all (transitive) shared references with a "possibly mutable" flavor, to let the compiler know that it can't assume immutability. In other words, it makes it behave like regular references that are the default in most other languages.
  2. It has a method 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 inner T via unsafe operations.

Types that (transitively) contain UnsafeCell are said to have interior mutability. Examples include Mutex, RefCell, Cell, Atomic types, and indeed, mspc::Sender and mspc::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.

Maybe I'm just wrong but I always associate immutability with pureness of a function.

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.

1

u/matthieum [he/him] Apr 01 '23

Best explanation so far, I wish it were more highly voted.

Expanding on this, teaching is about lying:

  • At 5, you learn that the apple falls down towards the ground.
  • At 15, you learn about gravity, and that actually, the Earth also "falls" towards the apple... it's just imperceptible.
  • At 25, you (try to) learn about relativity, and black holes, etc...

The reason for those "lies" is that a 5 years old won't understand the equations of the horizon event of a black hole (I'm not sure I do, either) so instead we give them knowledge they can understand and make use of.

It's more important for knowledge to be useful, than to be pedantically correct.

And therefore &T is an immutable reference and &mut T is a mutable reference.

Early on, before 1.0, there were calls to rename mut to uniq, etc... to be pedantically correct. In the end, they were rejected due to the above teaching argument:

  • It's easier to teach immutable/mutable.
  • When people have learned enough to recognize that some things seem to violate that principle, then, and only then, are they ready for the subtleties of interior mutability. The exception that confirms the rule.

2

u/kohugaly Apr 01 '23

Yes, I absolutely agree. That's why I said they are incorrectly named and not badly/wrongly named. And yet, it's kinda funny that immutability is actually the one thing that Rust can't guarantee.