How to work around self borrowing with map .or_ins

2019-02-23 17:10发布

问题:

This question already has an answer here:

  • Mutably borrow one struct field while borrowing another in a closure 2 answers

This snippet

use std::collections::HashMap;

struct Foo {
    local_ids: HashMap<i32, i32>,
    last_id: i32,
}

impl Foo {
    fn foo(&mut self, external_id: i32) {
        let id = self.local_ids
                     .entry(external_id)
                     .or_insert_with(||{self.last_id += 1; self.last_id});
    }
}

Doesn't work because we can't borrow self twice

error: closure requires unique access to `self` but `self.local_ids` is already borrowed [E0500]

Is this possible to fix without a second key lookup?


This is very similar to Rust: HashMap borrow issue when trying to implement find or insert, but the API has changed substantially.

The find_with_or_insert_with answer from above doesn't appear to map to the current api.

回答1:

The problem is that the closure captures self, whereas it only needs to capture a mutable reference to the last_id field.

Rust allows us to take independent mutable borrows on distinct fields, so we can use that to our advantage and pass a mutable reference to the last_id field to the closure.

use std::collections::HashMap;

struct Foo {
    local_ids: HashMap<i32, i32>,
    last_id: i32,
}

impl Foo {
    fn foo(&mut self, external_id: i32) {
        let last_id = &mut self.last_id;
        let id = self.local_ids
                     .entry(external_id)
                     .or_insert_with(|| { *last_id += 1; *last_id });
    }
}

When we use the expression self.last_id in the closure, the closure captures self directly, but Rust doesn't realize that the borrows are independent, so we need to be more explicit.