提问人:Ted Klein Bergman 提问时间:4/19/2021 最后编辑:Ted Klein Bergman 更新时间:4/26/2021 访问量:1712
当借用在方法调用后面时,如何借用两个不相交的字段?
How to borrow two disjoint fields when the borrow is behind a method call?
问:
在下面的代码中,我有一个带有只读字段和一堆读写字段的结构。直接从结构中借用单独的字段时,借用没有问题。但是,当我将借用隐藏在方法调用后面时,它说我不能再借用。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
有没有办法告诉编译器代码很好,我正在借用两个不相交的字段?或者有其他符合人体工程学的解决方案?
答:
可以使用的不同技术
使用拆分借用
此注释建议使用 Splitting Borrow 来借用字段。这将如以下示例所示。
但是,对于维护者来说,这不是一个符合人体工程学的 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 { }
}
}
使用内部可变性
此答案显示了如何通过将类型包装在 .如果我们将所有可变字段包装在 a 中并且只对 的不可变借用进行操作,这可能是一个解决方案。mut
std::cell::Cell
Cell
Foo
但是,这限制了数据是单线程的,因为实现!同步
。它还将数据限制为仅复制
。此外,这确实允许可变字段在我们传递了不可变引用的代码位置发生突变,因此期望它们不会发生突变。我不认为这是一个解决方案,但可以工作。std::cell::Cell
包装在 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 方法。
评论
b
main