Почему 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, Как только метод закончен, заимствование карты закончено.

Другие вопросы по тегам