0

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!

Bill Fraser
  • 908
  • 7
  • 14
  • Oh wow, yeah, that solves it. I didn't expect there to be an API for this, so I didn't look for one. I still think the lifetime of the borrow in an `if let` expression is just crazy... – Bill Fraser Jan 22 '16 at 23:48
  • FYI, the last bit of your question (why does `else` have a borrow) is a well-known limitation of the current implementation of the borrow checker, as it has *lexical scopes*. There are [plans to fix this](https://github.com/rust-lang/rfcs/issues/811). – Shepmaster Jan 22 '16 at 23:50

0 Answers0