web-dev-qa-db-fra.com

Comment parcourir un Hashmap, imprimer la clé / valeur et supprimer la valeur dans Rust?

Cela devrait être une tâche triviale dans n'importe quelle langue. Cela ne fonctionne pas à 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() {}

Voici l'erreur du compilateur:

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

Pourquoi essaie-t-il de déplacer une référence? D'après la documentation, je ne pensais pas que le déplacement/emprunt s'appliquait aux références.

16
adapt-dev

Il y a au moins deux raisons pour lesquelles cela est interdit:

  1. Vous devez avoir deux références mutables simultanées à map - une détenue par l'itérateur utilisé dans la boucle for et une dans la variable map pour appeler map.remove.

  2. Vous avez des références à la clé et à la valeur dedans la carte lorsque vous essayez de muter la carte. Si vous étiez autorisé à modifier la carte de quelque manière que ce soit, ces références pourraient être invalidées, ouvrant la porte à une sécurité de mémoire.

Un noyau Rust est Alias ​​XOR Mutabilité . Vous pouvez avoir plusieurs immuables des références à une valeur ou vous pouvez avoir une seule référence mutable.

Je ne pensais pas que le déplacement/emprunt s'appliquait aux références.

Chaque type est soumis aux règles de déplacement de Rust ainsi qu'à l'alias mutable. Veuillez nous indiquer quelle partie de la documentation indique que ce n'est pas le cas afin que nous puissions y remédier.

Pourquoi essaie-t-il de déplacer une référence?

Ceci est combiné de deux parties:

  1. Vous ne pouvez avoir qu'une seule référence mutable
  2. for boucles prendre la valeur pour itérer par valeur

Lorsque vous appelez for (k, v) in map {}, la propriété de map est transférée à la boucle for et a maintenant disparu.


J'effectuerais un emprunt immuable de la carte (&*map) Et répéterais cela. À la fin, j'effacerais le tout:

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

supprimer chaque valeur avec une clé qui commence par la lettre "A"

J'utiliserais HashMap::retain :

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

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

Cela garantit que key et value n'existent plus lorsque la carte est réellement modifiée, ainsi tout emprunt qu'ils auraient eu est maintenant parti.

15
Shepmaster

Cela devrait être une tâche triviale dans n'importe quelle langue.

La rouille vous empêche de muter la carte pendant que vous itérez dessus. Dans la plupart des langues, cela est autorisé, mais souvent le comportement n'est pas bien défini, et la suppression de l'élément peut interférer avec l'itération, compromettant son exactitude.

Pourquoi essaie-t-il de déplacer une référence?

HashMap implémente IntoIterator, donc votre boucle est équivalente à :

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

Si vous regardez la définition de into_iter , vous verrez qu'il faut self, pas &self ou &mut self. Votre variable map est une référence, elle est donc implicitement déréférencée pour arriver à self, c'est pourquoi l'erreur indique que *map a été déplacé.

L'API est intentionnellement construite de cette façon afin que vous ne puissiez rien faire de dangereux lors du bouclage sur une structure. Une fois la boucle terminée, la propriété de la structure est abandonnée et vous pouvez la réutiliser.

Une solution consiste à garder une trace des éléments que vous souhaitez supprimer dans un Vec, puis à les supprimer par la suite:

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

Vous pouvez également utiliser un itérateur pour filtrer la carte en une nouvelle. Peut-être quelque chose comme ça:

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

Mais je viens de voir le montage de Shepmaster - j'avais oublié retain, ce qui est mieux. Il est plus concis et ne fait pas de copie inutile comme je l'ai fait.

6
Peter Hall