正确拆分时 Rust 不允许可变借用
Rust not allowing mutable borrow when splitting properly
struct Test {
a: i32,
b: i32,
}
fn other(x: &mut i32, _refs: &Vec<&i32>) {
*x += 1;
}
fn main() {
let mut xes: Vec<Test> = vec![Test { a: 3, b: 5 }];
let mut refs: Vec<&i32> = Vec::new();
for y in &xes {
refs.push(&y.a);
}
xes.iter_mut().for_each(|val| other(&mut val.b, &refs));
}
虽然 refs
仅保存对 xes
中元素的 a
成员的引用,并且函数 other
使用 b
成员,生锈产生以下错误:
error[E0502]: cannot borrow `xes` as mutable because it is also borrowed as immutable
--> /src/main.rs:16:5
|
13 | for y in &xes {
| ---- immutable borrow occurs here
...
16 | xes.iter_mut().for_each(|val| other(&mut val.b, &refs));
| ^^^ mutable borrow occurs here ---- immutable borrow later captured here by closure
关闭有问题吗?通常 splitting borrows 应该允许这样做。我错过了什么?
拆分借用仅适用于一个函数。但是,在这里,您在 main
中借用字段 a
并在闭包中借用字段 b
(除了能够从外部范围使用和借用变量之外,它是一个独特的功能)。
从 Rust 1.43.1 开始,函数签名不能表达细粒度的借用;当引用(直接或间接)传递给函数时,它可以访问它的 all。跨函数的借用检查基于函数签名;这部分是为了性能(跨函数的推理成本更高),部分是为了确保随着函数的发展(尤其是在库中)的兼容性:构成函数有效参数的内容不应取决于函数的 实施.
据我了解,您的要求是您需要能够根据整个对象集的字段 a
的值更新对象的字段 b
。
我看到有两种方法可以解决这个问题。首先,我们可以在捕获对 a
的共享引用的同时捕获对 b
的所有可变引用。这是拆分借用的正确示例。这种方法的缺点是我们需要分配两个 Vec
来执行操作。
fn main() {
let mut xes: Vec<Test> = vec![Test { a: 3, b: 5 }];
let mut x_as: Vec<&i32> = Vec::new();
let mut x_bs: Vec<&mut i32> = Vec::new();
for x in &mut xes {
x_as.push(&x.a);
x_bs.push(&mut x.b);
}
x_bs.iter_mut().for_each(|b| other(b, &x_as));
}
下面是使用迭代器构建两个 Vec
的等效方法:
fn main() {
let mut xes: Vec<Test> = vec![Test { a: 3, b: 5 }];
let (x_as, mut x_bs): (Vec<_>, Vec<_>) =
xes.iter_mut().map(|x| (&x.a, &mut x.b)).unzip();
x_bs.iter_mut().for_each(|b| other(b, &x_as));
}
另一种方法是完全避免可变引用并改用内部可变性。标准库有 Cell
,适用于 Copy
类型,例如 i32
、RefCell
,适用于所有类型,但在运行时进行借用检查,增加了一些轻微的开销,以及 Mutex
和 RwLock
,它们可以在多个线程中使用,但在运行时执行锁定检查,因此最多一个线程可以随时访问内部值。
这是 Cell
的示例。我们可以用这种方法消除两个临时的 Vec
,我们可以将整个对象集合传递给 other
函数,而不仅仅是对 a
字段的引用。
use std::cell::Cell;
struct Test {
a: i32,
b: Cell<i32>,
}
fn other(x: &Cell<i32>, refs: &[Test]) {
x.set(x.get() + 1);
}
fn main() {
let xes: Vec<Test> = vec![Test { a: 3, b: Cell::new(5) }];
xes.iter().for_each(|x| other(&x.b, &xes));
}
struct Test {
a: i32,
b: i32,
}
fn other(x: &mut i32, _refs: &Vec<&i32>) {
*x += 1;
}
fn main() {
let mut xes: Vec<Test> = vec![Test { a: 3, b: 5 }];
let mut refs: Vec<&i32> = Vec::new();
for y in &xes {
refs.push(&y.a);
}
xes.iter_mut().for_each(|val| other(&mut val.b, &refs));
}
虽然 refs
仅保存对 xes
中元素的 a
成员的引用,并且函数 other
使用 b
成员,生锈产生以下错误:
error[E0502]: cannot borrow `xes` as mutable because it is also borrowed as immutable
--> /src/main.rs:16:5
|
13 | for y in &xes {
| ---- immutable borrow occurs here
...
16 | xes.iter_mut().for_each(|val| other(&mut val.b, &refs));
| ^^^ mutable borrow occurs here ---- immutable borrow later captured here by closure
关闭有问题吗?通常 splitting borrows 应该允许这样做。我错过了什么?
拆分借用仅适用于一个函数。但是,在这里,您在 main
中借用字段 a
并在闭包中借用字段 b
(除了能够从外部范围使用和借用变量之外,它是一个独特的功能)。
从 Rust 1.43.1 开始,函数签名不能表达细粒度的借用;当引用(直接或间接)传递给函数时,它可以访问它的 all。跨函数的借用检查基于函数签名;这部分是为了性能(跨函数的推理成本更高),部分是为了确保随着函数的发展(尤其是在库中)的兼容性:构成函数有效参数的内容不应取决于函数的 实施.
据我了解,您的要求是您需要能够根据整个对象集的字段 a
的值更新对象的字段 b
。
我看到有两种方法可以解决这个问题。首先,我们可以在捕获对 a
的共享引用的同时捕获对 b
的所有可变引用。这是拆分借用的正确示例。这种方法的缺点是我们需要分配两个 Vec
来执行操作。
fn main() {
let mut xes: Vec<Test> = vec![Test { a: 3, b: 5 }];
let mut x_as: Vec<&i32> = Vec::new();
let mut x_bs: Vec<&mut i32> = Vec::new();
for x in &mut xes {
x_as.push(&x.a);
x_bs.push(&mut x.b);
}
x_bs.iter_mut().for_each(|b| other(b, &x_as));
}
下面是使用迭代器构建两个 Vec
的等效方法:
fn main() {
let mut xes: Vec<Test> = vec![Test { a: 3, b: 5 }];
let (x_as, mut x_bs): (Vec<_>, Vec<_>) =
xes.iter_mut().map(|x| (&x.a, &mut x.b)).unzip();
x_bs.iter_mut().for_each(|b| other(b, &x_as));
}
另一种方法是完全避免可变引用并改用内部可变性。标准库有 Cell
,适用于 Copy
类型,例如 i32
、RefCell
,适用于所有类型,但在运行时进行借用检查,增加了一些轻微的开销,以及 Mutex
和 RwLock
,它们可以在多个线程中使用,但在运行时执行锁定检查,因此最多一个线程可以随时访问内部值。
这是 Cell
的示例。我们可以用这种方法消除两个临时的 Vec
,我们可以将整个对象集合传递给 other
函数,而不仅仅是对 a
字段的引用。
use std::cell::Cell;
struct Test {
a: i32,
b: Cell<i32>,
}
fn other(x: &Cell<i32>, refs: &[Test]) {
x.set(x.get() + 1);
}
fn main() {
let xes: Vec<Test> = vec![Test { a: 3, b: Cell::new(5) }];
xes.iter().for_each(|x| other(&x.b, &xes));
}