I spent an afternoon this week losing an argument with rustc, which is becoming a familiar shape of afternoon. I was iterating over a collection and trying to modify it inside the loop, the most natural thing in the world, and the borrow checker simply would not have it. Cannot borrow as mutable because it is also borrowed as immutable. I read that message about nine times with increasing indignation.
My first instinct, every time, is that the compiler is being pedantic. It is not. After the fourth attempt to wrestle it into compiling, I stopped and actually thought about what I was asking for: read the thing and change the thing at the same time, which in a language without a babysitter is exactly the bug that bites you six months later when the collection reallocates underneath you and the iterator points at freed memory. The borrow checker was not being difficult. It was pointing at a genuine mistake I had not noticed I was making.
The fix was dull and correct. Collect the changes I wanted into a separate Vec, finish the read, then apply them in a second pass. Two clean borrows instead of one tangled one.
let updates: Vec<_> = items.iter()
.filter(|i| i.needs_update())
.map(|i| i.id)
.collect();
for id in updates {
items.get_mut(&id).map(|i| i.touch());
}
Slightly more code. Noticeably more honest about what it is doing. And it would never have let me write the latent bug I was halfway into committing.
I have come round to thinking "losing gracefully" to the borrow checker is the whole point. It is not a fight you are meant to win by being cleverer; it is a fight you lose by being made to write the safe version instead of the convenient one. Some days that is maddening. Most days, with a little distance, I am quietly grateful the bug got caught at compile time by a machine rather than at three in the morning by a pager.