rust-unofficial / patterns

A catalogue of Rust design patterns, anti-patterns and idioms
https://rust-unofficial.github.io/patterns/
Mozilla Public License 2.0
7.84k stars 354 forks source link

View type #343

Closed arialpew closed 1 year ago

arialpew commented 1 year ago

Borrowck doesn't support partial borrow across regular function boundary.

View type aim to solve this problem.

With a classical approach where struct fields are private, Borrowck fail to reason about partial borrow across regular function boundary. If you start to write getters, you'll likely encounter a situation where you want to borrow disjoint fields of your struct across function call. You can't : a single getter lock the entiere memory region of the struct for Borrowck.

View type encapsulate another type and act as a readonly view. This is so far the cleanest and most ergonomic solution, as shown in the example below. As all fields are now public, Borrowck 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).

This pattern is already used in numerous code base.

#![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.
    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.
    }
}