Borrow as mutable more than once in if let else

Why does the following code raises an error[E0499]: cannot borrow *nested as mutable more than once at a time

fn insert<'a>(
    nested: &'a mut HashMap<K1, HashMap<K2, V>>,
    first: &'_ K1,
    second: K2,
    value: V,
) -> &'a V {
    let inner = if let Some(x) = nested.get_mut(first) {
        x
    } else {
        nested.entry(first.to_owned()).or_default()
    };
    inner.entry(second).or_insert(value)
}

(playground link)

The two mutable borrows (get_mut and entry on nested) are happening sequentially.
No closure de-sugaring involved either, like in Borrow mutably, then borrow mutably again

P.S. I'm trying to prevent an extra clone first.to_owned when entry exists, not sure if it is worth it.

x is a reference into nested, so it keeps the first borrow alive when it gets assigned to inner, which is used after the if. The .entry call in the else happens while the first &mut exists, and causes the error.

I’ve think I’ve seen someone saying this (or something like it) should be allowed by the next-generation borrow checker once it’s ready. Don’t quote me on that, though.

This works, but at the cost of a double lookup:

fn insert<'a>(
    nested: &'a mut HashMap<K1, HashMap<K2, V>>,
    first: &'_ K1,
    second: K2,
    value: V,
) -> &'a V {
    let inner = if nested.contains_key(first) {
        nested.get_mut(first).unwrap()
    } else {
        nested.entry(first.to_owned()).or_default()
    };
    inner.entry(second).or_insert(value)
}

If your keys are small, and especially if they’re Copy, it’s probably better to just use the entry API for both cases:

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] struct K1(i32);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] struct K2(i32);

/* ... */

fn insert<'a>(
    nested: &'a mut HashMap<K1, HashMap<K2, V>>,
    first: K1,
    second: K2,
    value: V,
) -> &'a V {
    nested.entry(first).or_default().entry(second).or_insert(value)
}
1 Like

Would it be better if the entry API took a Cow<K>?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.