Search code examples
ruststructborrow-checkermutability

Borrowing parts of structs without RefCell


According to this question, it is not possible to borrow parts of a struct, only the entire struct. This makes sense, however I would not expect the following code (playground) to compile, which it does:

struct Struct (u32, u32);

fn foo(a: &mut u32, b: &u32) {
}

fn main() {
    let mut s = Struct ( 1, 2 );
    foo(&mut s.0, &s.1);
}

Why does this work?

Additionally, is there any way to get the compiler to make the same differentiation between borrowing members when some indirection is introduced via. a method, without using a RefCell or other run-time checking. See (playground):

struct Struct (u32, u32);

impl Struct {
    fn a(&mut self) -> &mut u32 {
        &mut self.0
    }
    
    fn b(&self) -> &u32 {
        &self.1
    }
}

fn foo(a: &mut u32, b: &u32) {
}

fn main() {
    let mut s = Struct ( 1, 2 );
    foo(s.a(), s.b());
}

At the moment this fails with:

error[E0502]: cannot borrow `s` as immutable because it is also borrowed as mutable
  --> src/main.rs:18:16
   |
18 |     foo(s.a(), s.b());
   |     --- -----  ^^^^^ immutable borrow occurs here
   |     |   |
   |     |   mutable borrow occurs here
   |     mutable borrow later used by call

I realise this is a bit of an obtuse example, my real case involves borrowing a number of members so the borrowing methods' implementations are more complex. There are still no members which are borrowed by both functions however.


Solution

  • Rust very deliberately does not extend its inference powers across function boundaries. This helps in making code more forwards compatible as you only need to keep the signature consistent and not its internals.

    For example, consider your own code. If you decided later that a() should actually return a reference to self.1 then it would break all the code that used a() and b() together like you did. With the current limitations, you can easily change what field the reference comes from and not have to worry about breaking anyone.

    Unfortunately, this makes what you want to do impossible.

    I suggest giving the problem a higher level look. Do a() and b() really belong together on the same struct? Would it perhaps be better to split the two fields into their own structs?

    Ideally, when you take a mutable reference to a struct, you would be using all (or most) of the struct, and there would be no need for someone else to be using that struct.