Returning a reference from a HashMap or Vec causes a borrow to last beyond the scope it’s in?

This is a known issue that will be solved by a future iteration of non-lexical lifetimes, but is not currently handled as of Rust 1.57.

If you are inserting to the same key that you are looking up, I’d encourage you to use the entry API instead.

You can add a smidgen of inefficiency to work around this for now. If the inefficiency is unacceptable, there are deeper workarounds.

HashMap

The general idea is to add a boolean that tells you if a value was present or not. This boolean does not hang on to a reference, so there is no borrow:

use std::collections::BTreeMap;

fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> {
    if map.contains_key(&key) {
        return map.get(&key);
    }

    map.insert(0, 0);
    None
}

fn main() {
    let mut map = BTreeMap::new();
    do_stuff(&mut map, 42);
    println!("{:?}", map)
}

Vec

Similar cases can be solved by using the index of the element instead of the reference. Like the case above, this can introduce a bit of inefficiency due to the need to check the slice bounds again.

Instead of

fn find_or_create_five<'a>(container: &'a mut Vec<u8>) -> &'a mut u8 {
    match container.iter_mut().find(|e| **e == 5) {
        Some(element) => element,
        None => {
            container.push(5);
            container.last_mut().unwrap()
        }
    }
}

You can write:

fn find_or_create_five<'a>(container: &'a mut Vec<u8>) -> &'a mut u8 {
    let idx = container.iter().position(|&e| e == 5).unwrap_or_else(|| {
        container.push(5);
        container.len() - 1    
    });
    &mut container[idx]
}

Non-Lexical Lifetimes

These types of examples are one of the primary cases in the NLL RFC: Problem case #3: conditional control flow across functions.

Unfortunately, this specific case isn’t ready as of Rust 1.57. If you opt in to the experimental -Zpolonius feature in nightly (RUSTFLAGS="-Z polonius" cargo +nightly check), each of these original examples compile as-is:

use std::collections::BTreeMap;

fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> {
    if let Some(key) = map.get(&key) {
        return Some(key);
    }

    map.insert(0, 0);
    None
}
fn find_or_create_five(container: &mut Vec<u8>) -> &mut u8 {
    match container.iter_mut().find(|e| **e == 5) {
        Some(element) => element,
        None => {
            container.push(5);
            container.last_mut().unwrap()
        }
    }
}

See also:

Leave a Comment