rust - 当借用在方法调用之后时如何借用两个不相交的字段?

标签 rust borrow-checker

在下面的代码中,我有一个结构体 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`.   
    }
}
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
有什么方法可以告诉编译器代码很好并且我借用了两个不相交的字段?或者还有其他符合人体工程学的解决方案吗?

最佳答案

可以使用的不同技术
使用 Splitting Borrow
This comment建议使用拆分借用来借用字段。这将如下面的示例所示工作。
但是,对于维护者的用户来说,这不是符合人体工程学的 API。如果他们在 foo 中借用了字段现在还想借a ,他们必须重写他们的借用方法才能通过 Split Borrow 方法。他们还必须匹配他们想要借用的字段。由于它们与元组匹配,因此并不完全清楚它们与哪些字段匹配。
此外,在 Foo 中引入了一个新的公共(public)领域会破坏一切,作为 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 { }
    }
}
Rust Playground
使用 Interior mutability
This answer展示了如何模拟使用 mut 的旧构造在字段中通过将类型包装在 std::cell::Cell 中.如果我们将所有可变字段包装在 Cell 中,这可能是一个解决方案。并且只对 Foo 的不可变借用进行操作.
但是,这将数据限制为单线程,如 std::cell::Cell工具 !Sync .它还限制数据仅为 Copy .此外,这确实允许可变字段在我们传递不可变引用的代码位置发生变异,因此期望它们不会发生变异。我不认为这是一个解决方案,但可以工作。
包装在只读类型中
This answer显示如何将只读值包装到不可变结构中。这是迄今为止最干净、最符合人体工程学的解决方案,如下面的示例所示。由于所有字段现在都是公开的,借用检查器能够确定我们实际上是在借用不相交的字段。
唯一的不便是您需要定义 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.
    }

}
Rust Playground
TL; 博士
单个字段的只读访问器或“getter”很容易破坏有效借用。相反,字段应该被包装在只读结构中,或者如果字段数量很少,则应该提供拆分借用方法。

关于rust - 当借用在方法调用之后时如何借用两个不相交的字段?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67154204/

相关文章:

rust - 查看 `for` 循环内的迭代器

rust - 可变引用有移动语义吗?

rust - 在 while 循环中更新可变 HashMap

rust - StructOpt - 如何为 Vec 提供默认值?

rust - 什么时候使用没有 Arc 的 Mutex?

rust - 替换 Rust 中的值导致 "cannot move out of borrowed content"

rust - 借用 Rust 中的检查器和函数参数,正确还是过于热心?

rust - 紫晶 rust : 'Cannot insert multiple systems with the same name ("parent_hierarchy_system") when running pong tutorial

rust - vector 的所有权转移会改变它在 Rust 中的内存位置吗?

rust - 你能为旧的(Redhat 5 vintage)Linux 构建 Rust 吗?