Rust заимствовать HashMap длится за пределами его возможностей?
У меня есть постоянная ошибка компиляции, когда Rust жалуется, что у меня есть неизменный заем, пока я пытаюсь получить изменчивый заем, но неизменный заем принадлежит другой области, и я ничего не переношу из этого.
У меня есть некоторый код, который проверяет значение на карте, и, если оно присутствует, возвращает его, в противном случае необходимо изменить карту различными способами. Проблема в том, что я не могу найти способ получить Rust, позволяющий мне делать оба, хотя эти две операции полностью разделены.
Вот некоторый бессмысленный код, который следует той же структуре, что и мой код, и демонстрирует проблему:
fn do_stuff(map: &mut BTreeMap<i32, i32>, key: i32) -> Option<&i32> {
// extra scope in vain attempt to contain the borrow
{
match map.get(&key) { // borrow immutably
Some(key) => { return Some(key); },
None => (),
}
}
// now I'm DONE with the immutable borrow, but rustc still thinks it's 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/main.rs:17:5
|
7 | match map.get(&key) { // borrow immutably
| --- immutable borrow occurs here
...
17 | map.insert(0, 0); // borrow mutably, which errors
| ^^^ mutable borrow occurs here
18 | None
19 | }
| - immutable borrow ends here
Это не имеет никакого смысла для меня. Как неизменный заем переживает этот масштаб?! Одна ветвь этого match
выходит из функции через return, а другой ничего не делает и покидает область видимости.
Я уже видел это раньше, когда я ошибочно занимался контрабандой заимствований за пределы какой-либо другой переменной, но здесь это не так!
Правда, заимствование выходит за пределы области действия с помощью оператора return, но смешно, что блокирует заимствование еще дальше вниз в функции - программа не может вернуться и продолжать идти! Если я верну что-то еще там, ошибка исчезнет, поэтому я думаю, что это то, чем зацикливается заимствование. Это похоже на ошибку для меня.
К сожалению, я не смог найти способ переписать это, не нажимая на ту же ошибку, так что это особенно неприятная ошибка, если это так.
1 ответ
Это известная проблема, которая с большой вероятностью может быть решена с помощью нелексических областей, что само по себе основывается на MIR. Если так случится, что вы вставляете в тот же ключ, который ищете, я бы рекомендовал вам использовать API ввода.
Вы можете добавить немного неэффективности, чтобы обойти это сейчас. Общая идея состоит в том, чтобы добавить логическое значение, которое сообщает вам, присутствует ли значение или нет. Этот логический тип не привязан к ссылке, поэтому нет заимствований:
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)
}
Подобные случаи с вектором вместо HashMap
может быть решена с помощью индекса элемента вместо ссылки. Как и в случае выше, это может привести к некоторой неэффективности из-за необходимости снова проверять границы срезов.
Вместо
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]
}
Обратите внимание, что над нелексическими временами жизни работают. В nightly Rust оба исходных примера кода компилируются как есть, когда NLL включен:
#![feature(nll)]
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
}
#![feature(nll)]
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()
}
}
}