在同一个范围内的两个不同的闭包中不能可变地借入 [英] Can't borrow mutably within two different closures in the same scope

查看:54
本文介绍了在同一个范围内的两个不同的闭包中不能可变地借入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的目标是创建一个独立于基础数据结构的功能(特别是泛洪)。我试图通过传递两个闭包来做到这一点:一个用于查询,它不可变地借用一些数据,另一个用于变异,它不可变地借用相同的数据。

My goal is to make a function (specifically, floodfill) that works independent of the underlying data structure. I tried to do this by passing in two closures: one for querying, that borrows some data immutably, and another for mutating, that borrows the same data mutably.

示例(在 Rust Playground 上进行了测试):

Example (tested on 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

我确实提出了解决方案,但是这很丑。如果我将闭包合并为一个闭包,并使用参数指定我想要的行为,它将起作用:

I did come up with a solution, but it is very ugly. It works if I combine the closures into one and specify which behavior I want with a parameter:

// #![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);
}

有什么方法可以使我轻松地借用检查器,而无需这种奇怪的组合方式

Is there any way I can appease the borrow checker without this weird way of combining closures?

推荐答案

问题根源于以下事实:您知道的信息

The problem is rooted in the fact that there's information that you know that the compiler doesn't.

如评论中所述,当值具有不可更改的引用时,您不能对其进行更改-否则它将不会更改!碰巧您的函数需要一次不变地访问数据,然后再可变地访问数据,但是编译器并不从函数的 signature 得知。它所能说明的就是该函数可以按任意顺序和任意次数调用闭包,其中包括在更改后使用不可变数据。

As mentioned in the comments, you cannot mutate a value while there is a immutable reference to it — otherwise it wouldn't be immutable! It happens that your function needs to access the data immutably once and then mutably, but the compiler doesn't know that from the signature of the function. All it can tell is that the function can call the closures in any order and any number of times, which would include using the immutable data after it's been mutated.

I'我猜您的原始代码确实做到了—可能会在对其进行突变后循环并访问不可变数据。

I'm guessing that your original code indeed does that — it probably loops and accesses the "immutable" data after mutating it.

一种解决方案是停止捕获闭包中的数据。相反,将数据提升为函数和闭包的参数:

One solution is to stop capturing the data in the closure. Instead, "promote" the data to an argument of the function and the closures:

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,
    );
}

但是,我相信这样两个相互关联的闭包将导致贫困可维护性。取而代之的是给这个概念起个名字并作一个特征:

However, I believe that two inter-related closures like that will lead to poor maintainability. Instead, give a name to the concept and make a trait:

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),
    );
}

另请参见:

  • When I can use either Cell or RefCell, which should I choose?
  • Situations where Cell or RefCell is the best choice
  • Need holistic explanation about Rust's cell and reference counted types

RefCell Mutex 的运行时开销很小。如果您已配置文件并确定是瓶颈,则可以使用不安全的代码。常见的不安全警告是:现在由您(容易出错的程序员)来确保您的代码不会执行任何未定义的行为。这意味着您必须知道什么是和不是未定义的行为

RefCell and Mutex have a (small) amount of runtime overhead. If you've profiled and determined that to be a bottleneck, you can use unsafe code. The usual unsafe caveats apply: it's now up to you, the fallible programmer, to ensure your code doesn't perform any undefined behavior. This means you have to know what is and is not undefined behavior!

use std::cell::UnsafeCell;

fn main() {
    let data = UnsafeCell::new(0);

    foo(
        0,
        |n| unsafe { *data.get() == n },
        |n| unsafe { *data.get() += n },
    );
}

我是另一个容易犯错误的程序员,我相信这段代码是安全的,因为永远不会是数据的时间可变别名。但是,这需要了解 foo 的作用。如果它同时调用另一个闭包,则很可能会成为未定义的行为。

I, another fallible programmer, believe this code to be safe because there will never be temporal mutable aliasing of data. However, that requires knowledge of what foo does. If it called one closure at the same time as the other, this would most likely become undefined behavior.


  1. 没有理由为闭包引用通用闭包类型

  1. There's no reason to take references to your generic closure types for the closures

没有理由使用-> ()上的闭包类型,您可以忽略它。

There's no reason to use -> () on the closure type, you can just omit it.

这篇关于在同一个范围内的两个不同的闭包中不能可变地借入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆