8

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?

Ted Klein Bergman
  • 9,146
  • 4
  • 29
  • 50
  • 2
    https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=cbab545f8ad9725eac2312943d90f180 – Stargateur Apr 19 '21 at 01:40
  • @Stargateur that solutions idiom seems pretty common in rust, does it have an established name? – Michael Anderson Apr 19 '21 at 03:17
  • Isn't the reason here that the compiler does not look inside functions to see what parts of a struct they borrow so it can't verify that it's ok to borrow `b` in `main`? – Jonas Berlin Apr 19 '21 at 08:16
  • 1
    @MichaelAnderson [Splitting borrows](https://doc.rust-lang.org/nomicon/borrow-splitting.html) is probably the closest. – Masklinn Apr 19 '21 at 08:32
  • 1
    @JonasBerlin well sure but at the same time, adding borrow-splitting to the type system would commonly leak implementation details and embrittle APIs, so it's not an innocuous change. – Masklinn Apr 19 '21 at 08:34
  • @Masklinn great point, do you have any pointers to discussions about this? – Jonas Berlin Apr 19 '21 at 11:33
  • Returning multiple fields as in [the suggestion above](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=cbab545f8ad9725eac2312943d90f180) seems to imply that I have to always consider returning mutable borrows to the other public fields if I want to provide a immutable accessor for one. In other words, any time I want a field to be read-only (have just a getter), I have to return mutable borrows to the rest of the fields, just to get around the borrow-checker. Is this correct? – Ted Klein Bergman Apr 19 '21 at 12:46
  • @TedKleinBergman as usual you could provide both mutable and immutable split borrows cf e.g. slice which provides both [split_at](https://doc.rust-lang.org/std/primitive.slice.html#method.split_at) and [split_at_mut](https://doc.rust-lang.org/std/primitive.slice.html#method.split_at_mut) (though obviously for slices `split_at_mut` is way more necessary than `split_at` which is just a convenience). – Masklinn Apr 19 '21 at 14:07
  • @MichaelAnderson well, I don't think this particular example is very idiomatic, what would be the point to have a struct that return its private internal field. At this point, this struct should have public field. My snipped just show that you need to borrow them at the same time (in the same method) for the compiler valid the code. – Stargateur Apr 19 '21 at 18:42
  • @Masklinn Yes, but my problem is that if I want to to provide an immutable accessor for one (or more) field, then it seems like I have to add a split borrow method for the user to be able to borrow the other fields mutably (because, as you said, an immutable split borrow doesn't serve any purpose). So should I ignore accessors completely then and just write `borrow(&mut self) -> { (&self.a, &mut self.b, ..., &mut self.z) }` instead and let the user match against the fields they want? – Ted Klein Bergman Apr 19 '21 at 18:55
  • @Stargateur A bit too much information might have been cut off when making this example. Both fields are meant to be public, but the first field `a` should not be mutated directly, i.e. it should be a read-only field from the perspective of the user. – Ted Klein Bergman Apr 19 '21 at 18:57

1 Answers1

9

Different techniques that could be used

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 of 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 restrict the data to be single threaded, as std::cell::Cell implements !Sync. It also restrict 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 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> {
    pub 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

TL;DR

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

Ted Klein Bergman
  • 9,146
  • 4
  • 29
  • 50