Clippy `contains_key` followed by `insert` suggestion won't compile

Hi,
I'm using a BTreeMap to keep track of distances in a path finding algorithm and that is all working fine so far. As usual now I then cargo clippy as I've learnt a lot from its suggestions however this one won't compile so wondering if there's a better solution.

warning: usage of `contains_key` followed by `insert` on a `BTreeMap``

Which is sensible, and then suggests an way to use an Entry but that then hits the Borrow Checker.

Rather than paste the suggestion here, I've made a small contrived example in Playground

And the compile error from Clippy's suggestion:

error[E0502]: cannot borrow `distance` as immutable because it is also borrowed as mutable
  --> src/main.rs:31:22
   |
29 |     if let std::collections::btree_map::Entry::Vacant(e) = distance.entry(posfar) {
   |                                                            ---------------------- mutable borrow occurs here
30 |         if let Some(dist) = can_get_to(posnear, posfar) {
31 |             e.insert(distance[&posnear] + dist);
   |               ------ ^^^^^^^^ immutable borrow occurs here
   |               |
   |               mutable borrow later used by call

Which I understand - but is there a way to satisfy both Clippy and the Borrow Checker?

You aren't using Entry how it was designed to be used. You probably want or_insert_with() or similar.

Actually, looking at the Playground, that's not even the problem: the problem is the order. You are trying to borrow the map event though there's an entry that holds a mutable reference to it.

This works.

Clippy's suggestions may be wrong. In this case it would be worth filing a bug report (add the I-suggestion-causes-error label) since this should be checkable (distance.entry() takes an exclusive borrow of distance, so it can't be used with any other mention of distance inside of the block).

In the specific case, you could consider fetching distance[&posnear] before calling distance.entry(); this would be simpler code but would incur two map lookups even on failure, whereas your existing code performs 3 lookups on success and 1 on failure. The advantage of using entry is that it's harder to make logic errors, and that it's more efficient in the case where you do something rather than nothing with the entry.

3 Likes

Thanks. I did wonder - but I tend to assume my one incompetence first. I'll submit it for consideration.