I lost an hour to the borrow checker last week, and the annoying thing, the genuinely annoying thing, is that it was right and I was wrong. I keep promising myself I'll assume that from the start and I keep forgetting.
The setup was ordinary. I had a HashMap of counters, and I was walking some input, looking each key up, and updating it. Somewhere in the middle I also wanted to occasionally insert a new key based on what I'd just read. So I had a reference into the map, borrowed from a get_mut, and then a few lines later I tried to insert into the same map. The compiler said no, and it said it in that very particular way:
error[E0499]: cannot borrow `counts` as mutable more than once at a time
My first instinct, every single time, is that the borrow checker is being pedantic about something that's obviously fine. It is almost never being pedantic.
what it was actually telling me
The thing I wanted to do was hold a mutable reference into a HashMap and then mutate the map's structure. That's not a Rust inconvenience, that's a real bug in any language, it's just that most languages let you write it and then surprise you. If I insert while holding a reference to an existing entry, the map might reallocate its buckets, and my reference now points at memory that's been moved out from under it. Rust won't let the reference outlive the safety of holding it. C++ would have let me do it and handed me an iterator invalidation bug to find at runtime, on a Friday.
So the borrow checker wasn't blocking a clever pattern. It was blocking a use-after-free I'd written without noticing.
losing gracefully
The fix, once I stopped arguing, was to stop holding the reference across the mutation. Read what I needed, end the borrow, then do the insert. The shortest version was to just not be clever:
let current = *counts.get(&key).unwrap_or(&0);
if current == 0 {
counts.insert(key.clone(), 1);
} else {
*counts.get_mut(&key).unwrap() += 1;
}
It does a second lookup, which the version I was fighting for avoided, and for a moment that offended me. Then I remembered I was optimising a hash lookup in a loop that runs a few thousand times at startup and never again, and the offence passed. Later I learned the tidier idiom anyway, *counts.entry(key).or_insert(0) += 1, which does the whole thing in one borrow and one lookup and is what I should have reached for in the first place. The entry API exists precisely so you don't have to hold a reference across a maybe-insert. The language had already solved this; I just hadn't met the solution yet.
The lesson I keep relearning is that "losing gracefully" to the borrow checker mostly means assuming it has spotted something real before assuming it's wrong. An hour of fighting it, and the takeaway was the same as last time: when it rejects your code, the bug is usually in the code, not the checker. I'll remember that until roughly the next time.