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:
-
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.