Возврат ссылки из HashMap или Vec приводит к тому, что заем остается в силе за пределами его объема?

У меня есть постоянная ошибка компиляции, когда Rust жалуется, что у меня есть неизменный заем, пока я пытаюсь получить изменчивый заем, но неизменный заем принадлежит другой области, и я ничего не переношу.

У меня есть некоторый код, который проверяет значение на карте, и, если оно присутствует, возвращает его, в противном случае необходимо изменить карту различными способами. Проблема в том, что я не могу найти способ получить Rust, позволяющий мне делать оба, хотя эти две операции полностью разделены.

Вот некоторый бессмысленный код, который следует той же структуре, что и мой код, и демонстрирует проблему:

use std::collections::BTreeMap;

fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> {
    // extra scope in vain attempt to contain the borrow
    {
        // borrow immutably
        if let Some(key) = map.get(&key) {
            return Some(key);
        }
    }

    // now I'm DONE with the immutable borrow, but rustc still thinks it borrowed

    map.insert(0, 0); // borrow mutably, which errors
    None
}

Это ошибки с:

error[E0502]: cannot borrow '*map' as mutable because it is also borrowed as immutable
  --> src/lib.rs:14:5
   |
3  | fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> {
   |                  - let call the lifetime of this reference ''1'
...
7  |         if let Some(key) = map.get(&key) {
   |                            --- immutable borrow occurs here
8  |             return Some(key);
   |                    --------- returning this value requires that '*map' is borrowed for ''1'
...
14 |     map.insert(0, 0); // borrow mutably, which errors
   |     ^^^^^^^^^^^^^^^^ mutable borrow occurs here

Это не имеет никакого смысла для меня. Как неизменный заем переживает этот масштаб?! Одна ветвь этого match выходит из функции через return, а другая ничего не делает и покидает область действия.

Я видел это раньше, когда я ошибочно занимался контрабандой заимствований за пределы какой-либо другой переменной, но здесь это не так!

Правда, заимствование выходит за пределы области действия с помощью оператора return, но смешно, что это блокирует заимствование еще дальше вниз в функции - программа не может вернуться и продолжить работу! Если я верну что-то еще там, ошибка исчезнет, поэтому я думаю, что это то, что зацикливается на контроле заимствований. Это похоже на ошибку для меня.

К сожалению, я не смог найти способ переписать это, не нажимая на ту же ошибку, так что это особенно неприятная ошибка, если это так.

Ответ 1

Это известная проблема, которая будет решена с помощью нелексических времен жизни, что само по себе основывается на MIR. Если так случится, что вы вставляете в тот же ключ, который ищете, я бы рекомендовал вам использовать API входа.

Вы можете добавить немного неэффективности, чтобы обойти это сейчас.

HashMap

Основная идея состоит в том, чтобы добавить логическое значение, которое сообщает вам, присутствует ли значение или нет. Это логическое значение не зависит от ссылки, поэтому заимствовать не нужно:

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

Подобные случаи могут быть решены с помощью индекса элемента вместо ссылки. Как и в случае выше, это может привести к некоторой неэффективности из-за необходимости еще раз проверять границы срезов.

Вместо

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()
        }
    }
}

Ты можешь написать:

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

Эти типы примеров являются одним из основных случаев в NLL RFC: Проблемный случай № 3: поток условного управления по функциям.

К сожалению, этот конкретный случай не готов к Rust 1.34. Если вы -Zpolonius экспериментальную -Zpolonius ночью, каждый из этих оригинальных примеров будет скомпилирован как есть:

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()
        }
    }
}

Смотрите также: