What you typed is syntax sugar. This means that the compiler will expand it (like a macro) to properly handle the operation you’re trying to perform.
Consider the following expansion, which results in the exact same error.
Note: This is not exact, since i have not looked into the compiler but it shows the problem more clearly.

Do you see the issue now?
Firstly the match construct is expanded into a new scope, containing the borrows of map and key.
Then the returned optional is checked if it actually contains a value.

If yes, update that value… So far OK!

If no, insert that key with a new value… But this cannot work:
We need a mutable borrow of map to insert, but the borrow rule is that we CAN NOT have multiple mutable borrows at the same time. The compiler tells us there is still a mutable borrow in scope (it hasn’t been dropped yet).

-> The first borrow is made by calling get_mut() and the compiler inferred there that a mutable borrow is necessary.
-> That borrow is actually re-borrowed to create variable value_opt. For the re-borrow to be valid it’s necessary that its ‘parent borrow’ is still alive (in scope).
-> Dropping value_opt will also drop the borrow of map.

So to make this work we need to break the outer scope, which starts right after

let key = "Key".to_string();

You could do this by inserting a return; as last statement of the IF-TRUE block, but this is not always possible and requires a function context.
Alternatively you could set a local boolean value to true and end the scope. Underneath you could test the boolean and insert if necessary.
Or specifically built for HashMap; the Entry API.

OK, but can’t the compiler infer that i’m not using value_opt within the ELSE block? This example is perfectly valid!
Yes, it’s a situation where there are actually no issues with the logic.
No, the compiler cannot infer this because the language we use to talk to it (the Rust syntax) is not expressive enough to literally indicate we stopped using a variable. We can only tell the compiler which variables are valid for the entirety of its scope, so the compiler drops these variables only when their scope is closed.

! There have been made improvements with a new feature called NLL (Non-lexical-lifetimes). It does exactly what it sounds like; it internally tests if variables are still used or not and can drop them before their ACTUAL SCOPE ends. Using this NLL feature will allow the example from your original post to compile.

I have another question that relates to this one. Say I’m trying to create an iterator for my type Store that is internally backed by a HashMap. When a caller calls next(), the value comes from the map underneath and it’s removed from the map. Here’s what it looks like:

I’ve moved the first borrow into its own scope. That a borrow occurs in self.map.keys() should be invisible to self.map.remove? But I still see the same problem occur.Playground link to the code above.

The way this problem is fixed is by making first_key a Option<String> like so:

So I’m assuming the borrow to map is somehow tying in with the lifetime of first_key . I fail to understand why though?
My best guess:

Rust sees first_key as being a reference to an object and assigns a lifetime to it.

When the compiler sees first_key = Some(key) , it assumes it needs to keep the reference to map alive as long as first_key .

When self.map.remove in encountered, the compiler denies the mutable borrow because a reference to map is already alive.

Is that correct? (If my explanation is correct, NLL fixes this too, right?)

first_key holds a reference into the HashMap, and that reference is still alive and used when you call self.map.remove() - NLL does not fix this because there’s no real issue to fix here . Imagine the map’s internal storage is cleared or reallocated as part of remove() - compiler doesn’t know what remove() does exactly. If it did allow that, you’d be left holding a dangling reference while remove() is executing.