Как выполнить итерацию через Hashmap, напечатать ключ/значение и удалить значение в Rust?

Это должно быть тривиальной задачей на любом языке. Это не работает в Rust.

use std::collections::HashMap;

fn do_it(map: &mut HashMap<String, String>) {
    for (key, value) in map {
        println!("{} / {}", key, value);
        map.remove(key);
    }
}

fn main() {}

Здесь ошибка компилятора:

error[E0382]: use of moved value: `*map`
 --> src/main.rs:6:9
  |
4 |     for (key, value) in map {
  |                         --- value moved here
5 |         println!("{} / {}", key, value);
6 |         map.remove(key);
  |         ^^^ value used here after move
  |
  = note: move occurs because `map` has type `&mut std::collections::HashMap<std::string::String, std::string::String>`, which does not implement the `Copy` trait

Почему он пытается переместить ссылку? Из документации я не думал, что к ссылкам обращаются переходы/заимствования.

Ответ 1

Есть по крайней мере две причины, почему это запрещено:

  1. Вам нужно иметь две одновременные изменяемые ссылки на map - одну, удерживаемую итератором, используемым в цикле for и одну в переменной map для вызова map.remove.

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

Основным принципом Rust является Aliasing XOR Mutability. Вы можете иметь несколько неизменяемых ссылок на значение или иметь одну изменяемую ссылку на него.

Я не думаю, что перемещение/заимствование применимо к ссылкам.

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

Почему он пытается переместить ссылку?

Это состоит из двух частей:

  1. Вы можете иметь только одну изменяемую ссылку
  2. циклы for принимают значение для перебора по значению

Когда вы вызываете for (k, v) in map {}, владение map передается в цикл for и теперь исчезает.


Я выполняю неизменный заем карты (&*map) и перебираю это. В конце я бы все прояснил:

fn do_it(map: &mut HashMap<String, String>) {
    for (key, value) in &*map {
        println!("{} / {}", key, value);
    }
    map.clear();
}

удалить каждое значение с помощью ключа, который начинается с буквы "А"

Я бы использовал HashMap::retain:

fn do_it(map: &mut HashMap<String, String>) {
    map.retain(|key, value| {
        println!("{} / {}", key, value);

        !key.starts_with("a")
    })
}

Это гарантирует, что key и value больше не будут существовать, когда карта фактически модифицирована, таким образом, все заимствования, которые они имели бы, теперь ушли.

Ответ 2

Это должно быть тривиальной задачей на любом языке.

Ржавчина мешает вам мутировать карту во время ее повторения. В большинстве языков это разрешено, но часто поведение не является четко определенным, и удаление элемента может помешать итерации, нарушая ее правильность.

Почему он пытается переместить ссылку?

HashMap реализует IntoIterator, поэтому ваш цикл эквивалентен:

for (key, value) in map.into_iter() {
    println!("{} / {}", key, value);
    map.remove(key);
}

Если вы посмотрите на определение into_iter, вы увидите, что требуется self, а не &self или &mut self. Ваша переменная map является ссылкой, поэтому она неявно разыменовывается, чтобы попасть в self, поэтому ошибка говорит о том, что *map был перемещен.

API намеренно построен таким образом, чтобы вы не могли сделать ничего опасного при переходе по структуре. Как только цикл будет завершен, право собственности на структуру будет отказано, и вы сможете снова использовать его.

Одним из решений является отслеживание элементов, которые вы намерены удалить в Vec, а затем удалить их впоследствии:

fn do_it(map: &mut HashMap<String, String>) {
    let mut to_remove = Vec::new();
    for (key, value) in &*map {
        if key.starts_with("A") {
            to_remove.push(key.to_owned());
        }
    }
    for key in to_remove.iter() {
        map.remove(key);
    }
}

Вы также можете использовать итератор для фильтрации карты в новую. Возможно, что-то вроде этого:

fn do_it(map: &mut HashMap<String, String>) {
    *map = map.into_iter().filter_map(|(key, value)| {
        if key.starts_with("A") {
            None
        } else {
            Some((key.to_owned(), value.to_owned()))
        }
    }).collect();
}

Но я только что видел редактирование Shepmaster - я забыл о retain, что лучше. Это более краткий и не делает ненужного копирования, как я сделал.