2 votes

supprimer une entrée aléatoire de HashMap en rust

Je voudrais supprimer un élément aléatoire d'un fichier HashMap . Cependant, j'ai continué à obtenir cette erreur.

error[E0502]: cannot borrow `self.map` as mutable because it is also borrowed as immutable
  --> src/main.rs:23:9
   |
21 |         let key_to_delete = self.map.keys().skip(x).next().unwrap();
   |                             -------- immutable borrow occurs here
22 |         println!("key_to_delete: {:?}", key_to_delete);
23 |         self.map.remove(&key_to_delete);
   |         ^^^^^^^^^------^^^^^^^^^^^^^^^^
   |         |        |
   |         |        immutable borrow later used by call
   |         mutable borrow occurs here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0502`.

Cependant, si j'ajoute un .clone() à la fin de cette ligne, l'erreur a disparu. Bien que j'aie résolu ce problème (principalement par essais et erreurs), je ne comprends toujours pas pourquoi cela fonctionne. Plus précisément, pourquoi cette erreur apparaît-elle en premier lieu ? La référence immuable à self.map sont abandonnés après la ligne

let key_to_delete = self.map.keys().skip(x).next().unwrap()

finir de s'exécuter ? Dans cet exemple, la clé est de type i32 qui ne prend que 4 octets. Par conséquent, il est possible de cloner. Mais que se passe-t-il si la clé est un gros struct ou un clone n'est pas souhaitable pour d'autres raisons ? Comment alors résoudre ce problème ?

Une autre observation est que mon IDE (Intellij avec le plugin rust) a montré que le type de key_to_delete es &i32 si je n'ai pas .clone() y i32 si je le fais. Je ne suis pas sûr que ce soit important.

Toute clarification est appréciée.

Voici mon code.

use std::collections::HashMap;
use rand::{Rng, thread_rng};

#[derive(Debug)]
struct MyStruct {
    map: HashMap<i32, i32>,
}

impl MyStruct {
    fn new() -> Self {
        MyStruct { map: HashMap::new() }
    }

    fn add(&mut self, key: i32, value: i32) {
        self.map.insert(key, value);
        println!("after add: {:?}", self.map);
    }

    fn delete(&mut self) {
        let x: usize = thread_rng().gen_range(0..self.map.len());
        let key_to_delete = self.map.keys().skip(x).next().unwrap().clone(); // this "clone" is critical
        println!("key_to_delete: {:?}", key_to_delete);
        self.map.remove(&key_to_delete);
        println!("map after delete: {:?}", self.map);
    }
}

fn main() {
    let mut c = MyStruct::new();
    c.add(1, 2);
    c.add(3, 4);
    c.add(5, 6);
    c.delete();
}

2voto

fygesser Points 23

Lorsque vous appelez self.map.keys().skip(x).next().unwrap() vous n'obtenez pas une clé - vous obtenez une référence à une touche.

Rust garantit que cette référence reste valide aussi longtemps qu'elle est vivante - c'est l'idée même du vérificateur d'emprunt de Rust et de la sécurité de la mémoire en général. Dans notre cas, cela signifie que Rust garantit que la clé à l'intérieur de la carte (avec la valeur associée) sera présent dans la carte aussi longtemps que vos référence aux vies clés.

Mais la façon dont le compilateur Rust l'applique est plutôt brutale - il vous interdit simplement de changer l'état de la carte de quelque manière que ce soit aussi longtemps que votre référence aux vies clés. Par conséquent, vous ne pouvez malheureusement pas utiliser votre référence pour supprimer l'entrée correspondante de la carte.

Pour illustrer ce point, examinons une version légèrement modifiée de votre code :

fn delete(&mut self) { //not really a delete at this point
    let x: usize = thread_rng().gen_range(0..self.map.len());
    let key_to_delete = self.map.keys().skip(x).next().unwrap(); // this "clone" is gone
    self.map.insert(123, 456); // <- this will cause a compiler error
    println!("key_to_delete: {:?}", key_to_delete);
    // self.map.insert(123, 456);  // <- this will not cause a compiler error as the reference to the key no longer exists
    println!("map after delete: {:?}", self.map);
}

Ici, j'ai changé le remove à insert afin que nous conozca nous ne risquons pas que notre clé soit effacée. Et pourtant le code ne compile pas pour exactement la même raison que celle que vous avez signalée à l'origine.

1voto

minitech Points 87225

Il est au moins possible de supprimer une entrée arbitraire d'un fichier HashMap dont les clés n'implémentent pas Clone d'une manière quelque peu détournée, sur le Nightly actuel en utilisant l'expérimental hash_raw_entry fonctionnalité :

#![feature(build_hasher_simple_hash_one)]
#![feature(hash_raw_entry)]

use std::collections::hash_map::{HashMap, RawEntryMut};
use std::hash::{BuildHasher, Hash};

fn pop<K: Hash, V>(m: &mut HashMap<K, V>) -> Option<(K, V)> {
    let hash = m.hasher().hash_one(m.keys().next()?);

    match m.raw_entry_mut().from_hash(hash, |_| true) {
        RawEntryMut::Occupied(entry) => Some(entry.remove_entry()),
        RawEntryMut::Vacant(_) => unreachable!("no entry with hash of first key"),
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X