Search code examples
rustborrow-checker

How to borrow two disjoint fields when the borrow is behind a method call?


In the code below I have a struct Foo with a read-only field a and a bunch of read-write fields. When borrowing the separate fields directly from the struct there's no issue borrowing. However, when I hide the borrow behind a method call, it says I no longer can borrow.

#![allow(unused_variables)]
#![allow(unused_mut)]
#![allow(dead_code)]

struct Foo {
    a: Vec<i32>,      // Public read-only  field.
    pub b: Vec<f32>,  // Public read-write field.
    pub c: Vec<i32>,  // Public read-write field.
    // ... maybe more fields ...
    pub z: Vec<bool>, // Public read-write field.
}

impl Foo {
    pub fn new() -> Self {
        Self {
            a: vec![1, 2, 3],
            b: vec![1.0, 2.0, 3.0],
            c: vec![-3, 0, 3],
            z: vec![false, true],
        }
    }
    pub fn borrow_a(&self) -> &Vec<i32> {
        &self.a
    }
}

pub fn main() {
    let mut foo = Foo::new();
    
    {   // This is okay.
        let x     = &foo.a;      // Immutably borrow `a`.
        let mut y = &mut foo.b;  // Mutably borrow `b`.
        for i in x { }           // Immutably use `a`.   
    }


    {   // This creates an error.
        let x = foo.borrow_a();  // Immutably borrow `a`.
        let mut y = &mut foo.b;  // Mutably borrow `b`.
        for i in x { }           // Immutably use `a`.   
    }
}

Rust playground

error[E0502]: cannot borrow `foo.b` as mutable because it is also borrowed as immutable
  --> src/main.rs:39:21
   |
38 |         let x = foo.borrow_a();  // Immutably borrow `a`.
   |                 --- immutable borrow occurs here
39 |         let mut y = &mut foo.b;  // Mutably borrow `b`.
   |                     ^^^^^^^^^^ mutable borrow occurs here
40 |         for i in x { }           // Immutably use `a`.   
   |                  - immutable borrow later used here

Is there some way I can tell the compiler that the code is fine and I'm borrowing two disjoint fields? Or is there some other ergonomic solution?


Solution

  • Different techniques that could be used

    TL;DR

    Read-only accessors, or "getters", for individual fields will easily break valid borrowing. Instead, the fields should be wrapped in a ReadOnly structure, or a Split Borrow method should be provided if the amount of fields is low.

    Use Splitting Borrow

    This comment suggest using Splitting Borrow to borrow the fields. This will work as shown in the example below.

    However, this is not an ergonomic API for the user or for the maintainer. If they've borrowed fields in foo and now also want to borrow a, they'd have to rewrite their borrows to go through the Split Borrow method. They also have to match against the fields they want to borrow. Since they match against a tuple, it's not entirely clear which fields they're matching against.

    Also, introducing a new public field in Foo would break everything, as the signature of split_borrow would have to change.

    All in all, this can work when the amount of fields is low.

    #![allow(unused_variables)]
    #![allow(unused_mut)]
    #![allow(dead_code)]
    
    struct Foo {
        a: Vec<i32>,      // Public read-only  field.
        pub b: Vec<f32>,  // Public read-write field.
        pub c: Vec<i32>,  // Public read-write field.
        // ... maybe more fields ...
        pub z: Vec<bool>, // Public read-write field.
    }
    
    impl Foo {
        pub fn new() -> Self {
            Self {
                a: vec![1, 2, 3],
                b: vec![1.0, 2.0, 3.0],
                c: vec![-3, 0, 3],
                z: vec![false, true],
            }
        }
        pub fn split_borrow(&mut self) -> (&Vec<i32>, &mut Vec<f32>, &mut Vec<i32>, &mut Vec<bool>) {
            (&self.a, &mut self.b, &mut self.c, &mut self.z)
        }
    }
    
    pub fn main() {
        let mut foo = Foo::new();
        
        {   // This is okay.
            let (a, ref mut b, ..) = foo.split_borrow();
            for i in a { }
        }
        
        {   // This is okay.
            let (a, _, _, ref mut z) = foo.split_borrow();
            for i in a { }
        }
    
        {   // This is okay if we re-borrow the values
            // between each use.
            let (a, ref mut b, ..)   = foo.split_borrow();
            b.push(4.0);
            
            let (a, _, _, ref mut z) = foo.split_borrow();
            // Can't use b from this point.
            z.push(false);
            
            println!("{:?}, {:?}", a, z);
        }
    
        
        {   // It's not okay to mix-and-match variables
            // from different borrows, as they're exclusively
            // bound to `foo`.
            let (a, ref mut b, ..)   = foo.split_borrow();
            let (_, _, _, ref mut z) = foo.split_borrow();
            for i in a { }
        }
    }
    

    Rust Playground

    Use Interior mutability

    This answer shows how to emulate the old construct of using mut in fields by wrapping the type in a std::cell::Cell. This could be a solution if we were to wrap all the mutable fields in a Cell and only operate on immutable borrows of Foo.

    However, this restricts the data to be single-threaded, as std::cell::Cell implements !Sync. It also restricts the data to only be Copy. Furthermore, this does allow the mutable fields to mutate in places of the code where we've passed an immutable reference and therefore expect them to not be mutated. I don't see this as a solution, but it can work.

    Wrap in a ReadOnly type

    This answer shows how to wrap the read-only value into an immutable struct. This is so far the cleanest and most ergonomic solution, as shown in the example below. As all fields are now public, the borrow checker is able to figure out that we're actually borrowing disjoint fields.

    The only inconvenience is that you need to define the ReadOnly structure in each module. This is because you want get_mut to only be accessible by the structure that owns ReadOnly (in other words, get_mut can't be public).

    #![allow(unused_variables)]
    #![allow(unused_mut)]
    #![allow(dead_code)]
    
    use std::ops::Deref;
    
    struct ReadOnly<T> {
        data: T,
    }
    
    impl<T> ReadOnly<T> {
        fn new(data: T) -> Self {
            ReadOnly { data }
        }
        
        pub fn get(&self) -> &T {
            &self.data
        }
    
        // Private function for mutating the
        // data from within Foo itself.
        fn get_mut(&mut self) -> &mut T {
            &mut self.data
        }
    }
    
    impl<T> Deref for ReadOnly<T> {
        type Target = T;
    
        fn deref(&self) -> &Self::Target {
            &self.data
        }
    }
    
    
    
    struct Foo {
        pub a: ReadOnly<Vec<i32>>,  // Public read-only  field.
        pub b: Vec<f32>,            // Public read-write field.
        pub c: Vec<i32>,            // Public read-write field.
        // ... maybe more fields ...
        pub z: Vec<bool>,           // Public read-write field.
    }
    
    impl Foo {
        pub fn new() -> Self {
            Self {
                a: ReadOnly::new(vec![1, 2, 3]),
                b: vec![1.0, 2.0, 3.0],
                c: vec![-3, 0, 3],
                z: vec![false, true],
            }
        }
    }
    
    pub fn main() {
        let mut foo = Foo::new();
    
        {   // This now works.
            let x     = foo.a.get();  // Immutably borrow `a`.
            let mut y = &mut foo.b;   // Mutably borrow `b`.
            for i in x { }            // Immutably use `a`.   
        }
    
        
        {   // This is now erroneous.
            let mut x = &mut foo.a;    // Can still borrow ReadOnly as mutable.
            let mut y = &mut foo.b;    // Mutably borrow `b`.
            for i in x.iter_mut() { }  // Can't use `a` as mutable.
        }
    
    }
    

    Rust Playground