Different techniques that could be used
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
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.