18
votes

I have a struct containing two fields and I want to modify one field (mutable borrow) using another field (immutable borrow), but I get an error from the borrow checker.

For instance, the following code:

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

gives the following error:

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

What are the Rust ways of updating one field using another from within a closure?

2

2 Answers

25
votes

Usually the borrow checker can distinguish between the different fields of a structure, but this doesn't work within closures (lambdas).

Instead, borrow the second field outside the closure:

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

This recent blog post shows a very useful pattern for this kind of problem:

Sometimes, when I want to be very precise, I will write closures in a stylized way that makes it crystal clear what they are capturing. Instead of writing |v| ..., I first introduce a block that creates a lot of local variables, with the final thing in the block being a move closure (move closures take ownership of the things they use, instead of borrowing them from the creator). This gives complete control over what is borrowed and how. In this case, the closure might look like:

In other words, the borrows are defined right with the closure and moved into the closure. This makes it totally clear that their purpose is to provide the closure with borrowed values. In context of the original question the pattern would look like this:

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

A nice property of this code is that the borrow of field2 does not stick around after it is no longer used.