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.
let key = "Key".to_string();
{
let value_opt: Option<&mut u32> = map.get_mut(&key);
if value_opt.is_some() {
*value_opt.unwrap() += 1;
} else {
map.insert(key, 1);
}
}
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.