Почему HashMap::get_mut() становится владельцем карты для остальной части области?
У меня есть следующий код, который вставляет некоторые значения в HashMap, а затем возвращает их обратно:
use std::collections::HashMap;
fn things() {
let mut map = HashMap::new();
map.insert(5, "thing");
map.insert(4, "world");
map.insert(1, "hello");
let mut thing = map.remove(&5);
let mut world = map.get_mut(&4);
let mut hello = map.get_mut(&1);
}
Попытка скомпилировать этот код приводит к следующей ошибке:
error[E0499]: cannot borrow `map` as mutable more than once at a time
--> src/main.rs:10:21
|
9 | let mut world = map.get_mut(&4);
| --- first mutable borrow occurs here
10 | let mut hello = map.get_mut(&1);
| ^^^ second mutable borrow occurs here
11 | }
| - first borrow ends here
После просмотра документов API для обоих remove()
а также get_mut()
методы (к счастью, они довольно близки друг к другу!) из сигнатур методов мне ничего не стоит remove()
Метод не может позаимствовать карту для остальной части текущей области, в то время как get_mut()
метод делает.
Другая часть данных, которая у меня есть, которая также вводит меня в заблуждение, состоит в том, что этот код компилируется:
use std::collections::HashMap;
fn things() {
let mut map = HashMap::new();
map.insert(5, "thing");
map.insert(4, "world");
map.insert(1, "hello");
let mut thing = map.remove(&5);
map.get_mut(&4);
let mut hello = map.get_mut(&1);
}
Не сохраняется результат первого звонка get_mut()
не приводит ли карта к заимствованию для остальной части объема? Как я мог узнать это, посмотрев на документацию? Я что-то упускаю?
1 ответ
Хорошей новостью является то, что эта ошибка в конечном счете является ограничением текущей реализации средства проверки заимствований. В будущей версии Rust будут представлены нелексические времена жизни. Если они включены, исходный код будет работать как есть:
#![feature(nll)]
use std::collections::HashMap;
fn things() {
let mut map = HashMap::new();
map.insert(5, "thing");
map.insert(4, "world");
map.insert(1, "hello");
let mut thing = map.remove(&5);
let mut world = map.get_mut(&4);
let mut hello = map.get_mut(&1);
}
fn main() {}
Это потому, что компилятор будет умнее и увидит, что вы не используете world
к тому времени, когда вы доберетесь до map.get_mut(&1)
, так что больше не нужно иметь действительную ссылку.
Вы можете получить эквивалентный код в текущей версии Rust, добавив явную область видимости:
let mut thing = map.remove(&5);
{
let mut world = map.get_mut(&4);
}
let mut hello = map.get_mut(&1);
Почему
HashMap::get_mut()
взять на себя ответственность за карту
Это абсолютно не так. Право собственности - точный термин в коде Rust. Обратите внимание, что сообщение об ошибке конкретно говорит
предыдущий заем
map
происходит здесь
А заем не является собственностью. Если я одолжу твою машину, мне не принадлежит твоя машина.
Ваш реальный вопрос - "почему он заимствует это для остальной части объема". Давайте посмотрим на подпись:
fn get_mut<Q: ?Sized>(&mut self, k: &Q) -> Option<&mut V>
where
K: Borrow<Q>,
Q: Hash + Eq,
На словах это можно прочитать как
Дана изменчивая ссылка на HashMap (
&mut self
) и что-то, что можно использовать для поиска ключа (K: Borrow<Q>, Q: Hash + Eq
), возвращает изменяемую ссылку на значение, если оно совпадает (Option<&mut V>
)
Однако эта возвращаемая изменяемая ссылка будет что- то менять в HashMap
Вот почему это вообще ссылка. Вам разрешено иметь только несколько неизменных заимствований ИЛИ одно изменяемое заимствование за раз. Это предотвращает написание кода, который вызывает несоответствия и проблемы безопасности.
Давайте посмотрим на remove
:
fn remove<Q: ?Sized>(&mut self, k: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq,
Это возвращает собственное значение, а не ссылку на HashMap
, Как только метод закончен, заимствование карты закончено.