首页 > 解决方案 > 当借用在方法调用后面时,如何借用两个不相交的字段?

问题描述

在下面的代码中,我有一个Foo带有只读字段a和一堆读写字段的结构。当直接从结构中借用单独的字段时,借用没有问题。但是,当我将借用隐藏在方法调用后面时,它说我不再可以借用。

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

锈操场

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

有什么方法可以告诉编译器代码很好并且我借用了两个不相交的字段?还是有其他符合人体工程学的解决方案?

标签: rustborrow-checker

解决方案


可以使用的不同技术

使用拆分借款

此评论建议使用拆分借用来借用字段。这将如下面的示例所示工作。

然而,这对于维护者的用户来说并不是一个符合人体工程学的 API。如果他们已经借用了字段foo并且现在还想借用a,他们必须重写他们的借用以通过拆分借用方法。他们还必须匹配他们想要借用的字段。由于它们与元组匹配,因此并不完全清楚它们与哪些字段匹配。

此外,引入一个新的公共领域Foo会破坏一切,因为签名split_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 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 { }
    }
}

锈游乐场

使用内部可变性

这个答案mut显示了如何通过将类型包装在 a 中来模拟使用 in 字段的旧结构std::cell::CellCell如果我们将所有可变字段包装在 a 中并且仅对 的不可变借用进行操作,这可能是一个解决方案Foo

但是,这将数据限制为单线程,如std::cell::Cell实现!Sync。它还将数据限制为仅Copy. 此外,这确实允许可变字段在我们传递不可变引用的代码位置发生变异,因此期望它们不会发生变异。我不认为这是一个解决方案,但可以工作。

包装成 ReadOnly 类型

此答案显示了如何将只读值包装到不可变结构中。这是迄今为止最干净、最符合人体工程学的解决方案,如下例所示。由于所有字段现在都是公开的,借用检查器能够确定我们实际上是在借用不相交的字段。

唯一的不便是您需要ReadOnly在每个模块中定义结构。这是因为您希望get_mut只能由拥有的结构访问ReadOnly(换句话说,get_mut不能公开)。

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

}

锈游乐场

TL;博士

单个字段的只读访问器或“getter”很容易破坏有效的借用。相反,应该将字段包装在 ReadOnly 结构中,或者如果字段数量较少,则应提供 Split Borrow 方法。


推荐阅读