rustborrow-checkerborrowinginterior-mutability

Interior mutability vs data hiding to hold fixed the referant of a mutable borrow


If we run this then we correctly get the error "cannot assign to immutable field a.x".

If we remove the two // comments, and comment out this bad line, then we get the error "cannot assign to data in a & reference". This makes sense because &mut does not provide interior mutability. We can reborrow an &A freely, so this must not give mutable access, ala &&mut is &&.

If we remove both the // comments and the /* */ comments, then the whole thing compiles, permitting the bad line that violates our invariant that a.x must never be pointed to anything else.

pub struct A<'a> {
    pub x: &'a mut [u8; 3],
}

fn main() {
    let y = &mut [7u8; 3];
    let /*mut*/ a = A { x: &mut [0u8; 3] };
    a.x[0] = 3;
    a.x = y;  //// This must be prevented!
    {
        // let b = &/*mut*/ a;
        // b.x[1] = 2;
    }
    println!("{:?}", a.x);
}

How should one maintain this invariant that x must not be changed? We could make the field private while providing public dereferencing methods, except writing constructors for A in unacceptable.

We can avoid the obnoxious constructor by making a A a private member of a wrapper struct AA(A) which itself hosts the public dereferencing methods. Now AA needs a trivial constructor, but it does not need arguments for all fields of A, does not impact order of execution, etc. This becomes painful if we need some traits implemented for both A and AA though.

Yet, another approach would be to use interior mutability by working with Cell<A>, accessing it with Cell::replace, and putting it back later. This sounds highly problematic, but shows that more solutions exist.

Any cleaner approaches?


Solution

  • Rather than use a Cell<A> you could make the array inside the A contain Cell<u8>s:

    use std::cell::Cell;
    
    pub struct A<'a> {
        x: &'a [Cell<u8>; 3],
    }
    
    fn main() {
        // let y = &mut [7u8; 3];
        let a = A { x: &[Cell::new(0u8), Cell::new(0u8), Cell::new(0u8)] };
        a.x[0].set(3);
        // a.x = y;
        {
            let b = &a;
            b.x[1].set(2);
        }
        println!("{:?}", a.x);
    }
    

    This will still behave how you want, with the same performance, but now the a variable is immutable, so you can't change a.x. You also don't need to make the array reference mutable either.

    The slight downside with your example is that you can't use the array repetition syntax, since Cell<T> does not implement Copy. This seems like an omission, but there is some explanation as to why that is here.