What does multiply-aliased mean?

The following doc has a statement that I am not sure if I get it correctly: "[Because] the contained values [of Rc and Arc] may be multiply-aliased, they can only be borrowed with &, not &mut: std::cell - Rust
Can you please explain what does multiply-aliased mean in that context and why they can only be borrowed with &?

This means that there can be two different Rc or Arc which point to the same underlying data (such is the reason for their existence).

  • multiply = there are multiple.
  • aliased = this is a sort of computer jargon definition, so looking at the regular dictionary definition probably isn't very helpful. I think it's like, there are multiple "aliases" or "names", which I guess metaphorically refers to the variables, for this piece of data.

So, whereas Box implements both Deref and DerefMut, allowing for both &Box<T> -> &T and &mut Box<T> -> &mut T conversion, Arc and Rc only implement Deref, therefore only allowing &Arc<T> -> &T conversion, not &mut Arc<T> -> &mut T conversion.

Because:

  • Rust's borrowing rules prohibit there being multiple mutable references to the same data at the same time
  • With normal variables, the borrowing rules ensures this at compile time
  • But once you have multiple variables which might point to the same data (via Rc/Arc), the compiler can no longer guarantee that
  • So Arc just doesn't allow you to access its inner contents mutably

If you still want to access the inner content mutably, you need something which enforces the borrowing rules at runtime. Specifically, this would be a RefCell or Cell (not Sync/thread-safe, so you would use when you're using Rc), or Mutex or RwLock (Sync, so you would use when you're using Arc).

4 Likes

To give an idea why Rust(and many other languages) care about this, consider the following C code :-

int
foo(int *a, int *b)
{
    *b = 0;    // store
    *a = 1;    // store
    return *b; // load
}

If you can assume that a and b don't point to the same memory then you could optimize based on the assumption that the return will always be 0. However, if a and b could point to the same memory then you need to do the (more expensive) load from memory because the memory that b points to could be set to 1 by storing to the memory that a points to.

(P.S. this probably isn't the only reason, I'd wager having the potential for two things to point to the same memory also makes lifetime detection intractable. However it is a reason.)

2 Likes