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

前端 未结 1 1144
孤独总比滥情好
孤独总比滥情好 2020-11-22 04:56

I\'ve got a persistent compile error where Rust complains that I have an immutable borrow while I\'m trying to mutably borrow, but the immutable borrow is from another scope

相关标签:
1条回答
  • 2020-11-22 05:37

    This is a known issue that will be solved by non-lexical lifetimes, which is itself predicated on MIR. If it so happens that 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.

    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.34. If you opt in to the experimental -Zpolonius feature in nightly, 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:

    • How to update-or-insert on a Vec?

      This is the same problem without returning the reference, which does work with the implementation of NLL available in Rust 1.32.

    • Double mutable borrow error in a loop happens even with NLL on

      This problem but in a slightly more complicated case.

    • When is it necessary to circumvent Rust's borrow checker?

      The ultimate escape hatch.

    0 讨论(0)
提交回复
热议问题