I have a BTreeMap that I want to either update or add an entry to, depending on if a key is found or not. The value I'm adding or updating is potentially very large, so I want to move it, not copy it. However, Rust's borrow checker keeps me from doing this, no matter what I try.
Skeleton demo program:
use std::collections::BTreeMap;
#[derive(Debug)]
struct Thing {
value: String
}
fn add_or_update(map: &mut BTreeMap<i32, Thing>, key: i32, value: String) {
// ?????????????????????
// ?? it is a mystery ??
// ?????????????????????
}
fn main() {
let mut container = BTreeMap::<i32, Thing>::new();
add_or_update(&mut container, 42, "Hello!".to_string()); // add
add_or_update(&mut container, 42, "World!".to_string()); // update
println!("{:?}", container);
}
And here's how I've tried variously implementing add_or_update
:
This obviously fails, because map
is borrowed mutably for the whole match expression:
match map.get_mut(&key) {
Some(ref mut thing) => { thing.value = value; },
None => { map.insert(key, Thing { value: value }); },
}
This is a borrow violation because map
is borrowed for the entire if/else expression, not just the 'if' part of it:
if let Some(ref mut thing) = map.get_mut(&key) {
thing.value = value;
}
else {
map.insert(key, Thing {
value: value
});
}
This fixes the borrow violation, but also doesn't work because value
is now moved in two places that the Rust compiler doesn't see as independent any more:
let mut updated = false;
if let Some(ref mut thing) = map.get_mut(&key) {
updated = true;
thing.value = value;
}
if !updated {
map.insert(key, Thing {
value: value
});
}
And now I'm out of ideas. Is there any way to do this?
The last example would work if it wasn't for the fact that I need to move the value, not copy it.
The way that if let
borrows for the entire if/else expression is utterly maddening. Why on earth does it borrow for the "else" part?!? Code there is only run if the if let
binding failed!