不能在同一范围内的两个不同闭包内可变借用
Can't borrow mutably within two different closures in the same scope
我的目标是制作一个独立于底层数据结构工作的函数(具体来说,floodfill)。我试图通过传递两个闭包来做到这一点:一个用于查询,不可变地借用一些数据,另一个用于变异,可变地借用相同的数据。
示例(在 the Rust Playground 上测试):
#![feature(nll)]
fn foo<F, G>(n: i32, closure: &F, mut_closure: &mut G)
where
F: Fn(i32) -> bool,
G: FnMut(i32) -> (),
{
if closure(n) {
mut_closure(n);
}
}
fn main() {
let mut data = 0;
let closure = |n| data == n;
let mut mut_closure = |n| {
data += n;
};
foo(0, &closure, &mut mut_closure);
}
错误:(调试,每晚)
error[E0502]: cannot borrow `data` as mutable because it is also borrowed as immutable
--> src/main.rs:16:27
|
15 | let closure = |n| data == n;
| --- ---- previous borrow occurs due to use of `data` in closure
| |
| immutable borrow occurs here
16 | let mut mut_closure = |n| {
| ^^^ mutable borrow occurs here
17 | data += n;
| ---- borrow occurs due to use of `data` in closure
18 | };
19 | foo(0, &closure, &mut mut_closure);
| -------- borrow later used here
我确实想出了一个解决方案,但是很丑陋。如果我将闭包合并为一个并使用参数指定我想要的行为,它就会起作用:
// #![feature(nll)] not required for this solution
fn foo<F>(n: i32, closure: &mut F)
where
F: FnMut(i32, bool) -> Option<bool>,
{
if closure(n, false).unwrap() {
closure(n, true);
}
}
fn main() {
let mut data = 0;
let mut closure = |n, mutate| {
if mutate {
data += n;
None
} else {
Some(data == n)
}
};
foo(0, &mut closure);
}
有什么方法可以安抚借用检查员而不用这种奇怪的组合闭包的方式吗?
问题的根源在于,您知道编译器不知道的信息。
正如评论中所提到的,当存在对它的不可变引用时,您不能改变一个值——否则它就不是不可变的!碰巧您的函数需要一次不可变地访问数据,然后可变地访问数据,但编译器无法从函数的 signature 中获知这一点。它只能说明该函数可以以任何顺序和任意次数调用闭包,这包括在数据发生变化后使用不可变数据。
我猜你的原始代码确实是这样做的——它可能会循环并在变异后访问 "immutable" 数据。
Compile-time借用检查
一种解决方案是停止捕获闭包中的数据。相反,"promote" 函数参数和闭包的数据:
fn foo<T, F, G>(n: i32, data: &mut T, closure: F, mut mut_closure: G)
where
F: Fn(&mut T, i32) -> bool,
G: FnMut(&mut T, i32),
{
if closure(data, n) {
mut_closure(data, n);
}
}
fn main() {
let mut data = 0;
foo(
0,
&mut data,
|data, n| *data == n,
|data, n| *data += n,
);
}
但是,我认为这样的两个 inter-related 闭包会导致可维护性差。相反,给这个概念起个名字并做一个特征:
trait FillTarget {
fn test(&self, _: i32) -> bool;
fn do_it(&mut self, _: i32);
}
fn foo<F>(n: i32, mut target: F)
where
F: FillTarget,
{
if target.test(n) {
target.do_it(n);
}
}
struct Simple(i32);
impl FillTarget for Simple {
fn test(&self, n: i32) -> bool {
self.0 == n
}
fn do_it(&mut self, n: i32) {
self.0 += n
}
}
fn main() {
let data = Simple(0);
foo(0, data);
}
Run-time借用检查
因为您的代码暂时需要可变性(您只需要它在给定时间可变或不可变),您还可以从 compile-time 借用检查切换到 run-time 借用检查.如评论中所述,Cell
、RefCell
和 Mutex
等工具可用于此目的:
use std::cell::Cell;
fn main() {
let data = Cell::new(0);
foo(
0,
|n| data.get() == n,
|n| data.set(data.get() + n),
);
}
另请参阅:
不安全programmer-brain-time借用检查
RefCell
和 Mutex
有少量的运行时开销。如果您分析 并确定这是一个瓶颈,您可以使用不安全代码。通常的不安全注意事项适用:现在由您(易犯错误的程序员)来确保您的代码不执行任何未定义的行为。这意味着你必须知道什么是未定义的行为,什么不是未定义的行为!
use std::cell::UnsafeCell;
fn main() {
let data = UnsafeCell::new(0);
foo(
0,
|n| unsafe { *data.get() == n },
|n| unsafe { *data.get() += n },
);
}
我,另一个容易犯错的程序员,相信这段代码是安全的,因为永远不会有 data
的临时可变别名。但是,这需要了解 foo
的作用。如果它同时调用一个闭包和另一个闭包,这很可能会变成未定义的行为。
补充评论
没有理由引用闭包的通用闭包类型
没有理由在闭包类型上使用-> ()
,你可以省略它。
我的目标是制作一个独立于底层数据结构工作的函数(具体来说,floodfill)。我试图通过传递两个闭包来做到这一点:一个用于查询,不可变地借用一些数据,另一个用于变异,可变地借用相同的数据。
示例(在 the Rust Playground 上测试):
#![feature(nll)]
fn foo<F, G>(n: i32, closure: &F, mut_closure: &mut G)
where
F: Fn(i32) -> bool,
G: FnMut(i32) -> (),
{
if closure(n) {
mut_closure(n);
}
}
fn main() {
let mut data = 0;
let closure = |n| data == n;
let mut mut_closure = |n| {
data += n;
};
foo(0, &closure, &mut mut_closure);
}
错误:(调试,每晚)
error[E0502]: cannot borrow `data` as mutable because it is also borrowed as immutable
--> src/main.rs:16:27
|
15 | let closure = |n| data == n;
| --- ---- previous borrow occurs due to use of `data` in closure
| |
| immutable borrow occurs here
16 | let mut mut_closure = |n| {
| ^^^ mutable borrow occurs here
17 | data += n;
| ---- borrow occurs due to use of `data` in closure
18 | };
19 | foo(0, &closure, &mut mut_closure);
| -------- borrow later used here
我确实想出了一个解决方案,但是很丑陋。如果我将闭包合并为一个并使用参数指定我想要的行为,它就会起作用:
// #![feature(nll)] not required for this solution
fn foo<F>(n: i32, closure: &mut F)
where
F: FnMut(i32, bool) -> Option<bool>,
{
if closure(n, false).unwrap() {
closure(n, true);
}
}
fn main() {
let mut data = 0;
let mut closure = |n, mutate| {
if mutate {
data += n;
None
} else {
Some(data == n)
}
};
foo(0, &mut closure);
}
有什么方法可以安抚借用检查员而不用这种奇怪的组合闭包的方式吗?
问题的根源在于,您知道编译器不知道的信息。
正如评论中所提到的,当存在对它的不可变引用时,您不能改变一个值——否则它就不是不可变的!碰巧您的函数需要一次不可变地访问数据,然后可变地访问数据,但编译器无法从函数的 signature 中获知这一点。它只能说明该函数可以以任何顺序和任意次数调用闭包,这包括在数据发生变化后使用不可变数据。
我猜你的原始代码确实是这样做的——它可能会循环并在变异后访问 "immutable" 数据。
Compile-time借用检查
一种解决方案是停止捕获闭包中的数据。相反,"promote" 函数参数和闭包的数据:
fn foo<T, F, G>(n: i32, data: &mut T, closure: F, mut mut_closure: G)
where
F: Fn(&mut T, i32) -> bool,
G: FnMut(&mut T, i32),
{
if closure(data, n) {
mut_closure(data, n);
}
}
fn main() {
let mut data = 0;
foo(
0,
&mut data,
|data, n| *data == n,
|data, n| *data += n,
);
}
但是,我认为这样的两个 inter-related 闭包会导致可维护性差。相反,给这个概念起个名字并做一个特征:
trait FillTarget {
fn test(&self, _: i32) -> bool;
fn do_it(&mut self, _: i32);
}
fn foo<F>(n: i32, mut target: F)
where
F: FillTarget,
{
if target.test(n) {
target.do_it(n);
}
}
struct Simple(i32);
impl FillTarget for Simple {
fn test(&self, n: i32) -> bool {
self.0 == n
}
fn do_it(&mut self, n: i32) {
self.0 += n
}
}
fn main() {
let data = Simple(0);
foo(0, data);
}
Run-time借用检查
因为您的代码暂时需要可变性(您只需要它在给定时间可变或不可变),您还可以从 compile-time 借用检查切换到 run-time 借用检查.如评论中所述,Cell
、RefCell
和 Mutex
等工具可用于此目的:
use std::cell::Cell;
fn main() {
let data = Cell::new(0);
foo(
0,
|n| data.get() == n,
|n| data.set(data.get() + n),
);
}
另请参阅:
不安全programmer-brain-time借用检查
RefCell
和 Mutex
有少量的运行时开销。如果您分析 并确定这是一个瓶颈,您可以使用不安全代码。通常的不安全注意事项适用:现在由您(易犯错误的程序员)来确保您的代码不执行任何未定义的行为。这意味着你必须知道什么是未定义的行为,什么不是未定义的行为!
use std::cell::UnsafeCell;
fn main() {
let data = UnsafeCell::new(0);
foo(
0,
|n| unsafe { *data.get() == n },
|n| unsafe { *data.get() += n },
);
}
我,另一个容易犯错的程序员,相信这段代码是安全的,因为永远不会有 data
的临时可变别名。但是,这需要了解 foo
的作用。如果它同时调用一个闭包和另一个闭包,这很可能会变成未定义的行为。
补充评论
没有理由引用闭包的通用闭包类型
没有理由在闭包类型上使用
-> ()
,你可以省略它。