在闭包中借用一个结构体字段时可变地借用另一个字段

22
我有一个包含两个字段的结构体,我想要用其中一个字段(不可变借用)来修改另一个字段(可变借用),但是借用检查器给出了一个错误提示。
例如,以下代码:
struct Struct {
    field1: Vec<i32>,
    field2: Vec<i32>,
}

fn main() {
    let mut strct = Struct {
        field1: vec![1, 2, 3],
        field2: vec![2, 3, 4],
    };

    strct.field1.retain(|v| !strct.field2.contains(v));

    println!("{:?}", strct.field1);
}

出现以下错误:

error[E0502]: cannot borrow `strct.field1` as mutable because it is also borrowed as immutable
  --> src/main.rs:12:5
   |
12 |     strct.field1.retain(|v| !strct.field2.contains(v));
   |     ^^^^^^^^^^^^^------^---^^-----^^^^^^^^^^^^^^^^^^^^
   |     |            |      |    |
   |     |            |      |    first borrow occurs due to use of `strct` in closure
   |     |            |      immutable borrow occurs here
   |     |            immutable borrow later used by call
   |     mutable borrow occurs here

如何使用 Rust 的方式,在闭包内更新一个字段并使用另一个字段呢?

2个回答

29

通常,借用检查器可以区分结构体的不同字段,但是在闭包(lambda)内部无法实现。

相反,将第二个字段从闭包外部进行借用:

let field2 = &strct.field2;
strct.field1.retain(|v| !field2.contains(v));

1
提议的 RFC 2229(“Closures Capture Disjoint Fields”)将使闭包的捕获语义更加智能化,但它需要大量的工作和思考。 - Shepmaster

4
这篇最近的博客文章展示了一种解决这类问题的非常有用的模式:
有时候,当我想要非常精确时,我会以一种风格化的方式编写闭包,使其清晰地表明它们正在捕获什么。我不是写 |v| ...,而是首先引入一个块,创建很多局部变量,在块的最后一个位置放置一个move闭包(move闭包拥有它们使用的东西的所有权,而不是从创建者那里借用)。这样就完全控制了借用和如何使用。在这种情况下,闭包可能看起来像:
换句话说,借用是在闭包内部定义并移动到闭包中。这使得它们的目的是为闭包提供借用值变得非常清晰。在原问题的上下文中,该模式将如下所示:
strct.field1.retain({
    let field2 = &strct.field2;
    move |v| !field2.contains(v)
});

这段代码的一个好处是,field2的借用在使用完后不会继续存在。

现在我们有了非词法生命周期,所以现在不行。 - antoyo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接