我的目标是创建一个独立于底层数据结构的函数(特别是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);
}
如果没有这种奇怪的组合闭包的方法,我有什么方法可以安抚借用检查器?
答案 0 :(得分:4)
问题的根源在于你知道编译器不知道的信息。
正如评论中所提到的,当存在对它的不可变引用时,你不能改变一个值 - 否则它不会是不可变的!碰巧你的函数需要一次然后可变地访问数据,但是编译器不知道函数的签名。所有它可以告诉的是,该函数可以以任何顺序和任意次数调用闭包,其中包括在变异后使用不可变数据。
我猜你的原始代码确实这样做了 - 它可能会在变异之后循环并访问“不可变”数据。
一种解决方案是停止捕获闭包中的数据。相反,将数据“提升”为函数和闭包的参数:
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,
);
}
但是,我认为两个相互关联的闭包会导致可维护性差。相反,给这个概念命名并制作一个特征:
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);
}
因为你的代码对可变性有暂时的需求(你只需要在给定时间它是可变的或不可变的),你也可以从编译时借用检查切换到运行时借用检查。如评论中所述,可以使用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),
);
}
另见:
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
做什么。如果它与另一个同时调用一个闭包,那很可能会成为未定义的行为。
没有理由引用闭包的通用闭包类型
没有理由在闭包类型上使用-> ()
,你可以省略它。