2016-03-13 2 views
0

Quelle est la justification de conception pour fournir une fonction iter_mut pour HashMap mais pas HashSet dans Rust?Pourquoi HashMap a-t-il iter_mut() mais pas HashSet?

Serait-ce un faux pas de rouler le sien (en supposant que cela peut même être fait)?

Avoir on pourrait atténuer les situations qui donnent lieu à

précédent X emprunt se produit ici; l'immuable empêche emprunter mouvements ultérieurs ou emprunte mutables de X jusqu'à la fin emprunter

Exemple

An extremely convoluted example (Gist) qui ne montre cas pas pourquoi le passage de paramètres est la façon dont il est. A un bref commentaire expliquant le point de la douleur:

use std::collections::HashSet; 

fn derp(v: i32, unprocessed: &mut HashSet<i32>) { 
    if unprocessed.contains(&v) { 

     // Pretend that v has been processed 
     unprocessed.remove(&v); 
    } 
} 

fn herp(v: i32) { 
    let mut unprocessed: HashSet<i32> = HashSet::new(); 
    unprocessed.insert(v); 

    // I need to iterate over the unprocessed values 
    while let Some(u) = unprocessed.iter().next() { 

     // And them pass them mutably to another function 
     // as I will process the values inside derp and 
     // remove them from the set. 
     // 
     // This is an extremely convoluted example but 
     // I need for derp to be a separate function 
     // as I will employ recursion there, as it is 
     // much more succinct than an iterative version. 
     derp(*u, &mut unprocessed); 
    } 
} 

fn main() { 
    println!("Hello, world!"); 
    herp(10); 
} 

La déclaration

while let Some(u) = unprocessed.iter().next() { 

est un emprunt immuable, d'où

derp(*u, &mut unprocessed); 

est impossible que l'état brut ne peut pas être emprunté mutably. L'emprunt immuable ne se termine pas avant la fin de la boucle while.

J'ai essayé d'utiliser this as reference et j'ai fini par essayer de tromper le vérificateur d'emprunts par diverses permutations d'assignations, en fermant des accolades, mais en raison du couplage des expressions voulues, le problème demeure.

+0

Notez que 'tout laisser entrer (u) = unprocessed.iter() suivant()' est équivalent à 'pour u in & –

+0

Vous avez non transformées sont une autre question ici. Vous ne pouvez pas emprunter' mutably alors que non transformées sont itérer dessus/en y incluant des références. Pourquoi ne pas simplement avoir la boucle * consommer * non traitée et simplifier la logique de votre programme? –

Répondre

2

Si votre HashSet contient un type Copy, comme i32, vous pouvez travailler sur une copie de la valeur pour libérer l'emprunt sur le HashSet tôt. Pour ce faire, vous devez éliminer tous les emprunts des liaisons dans l'expression while let. Dans votre code d'origine, u est de type &i32, et il continue d'emprunter de unprocessed jusqu'à la fin de la boucle. Si nous changeons le motif en Some(&u), alors u est de type i32, qui n'emprunte rien, donc nous sommes libres d'utiliser unprocessed comme nous le souhaitons.

fn herp(v: i32) { 
    let mut unprocessed: HashSet<i32> = HashSet::new(); 
    unprocessed.insert(v); 

    while let Some(&u) = unprocessed.iter().next() { 
     derp(u, &mut unprocessed); 
    } 
} 

Si le type est Copy ou est trop cher pour copier/clone, vous pouvez les envelopper dans Rc ou Arc, et les cloner que vous itérer sur leur utilisation cloned() (le clonage d'un Rc ou Arc ne le fait pas clone la valeur sous-jacente, il clone simplement le pointeur Rc et incrémente le compteur de référence).

use std::collections::HashSet; 
use std::rc::Rc; 

fn derp(v: &i32, unprocessed: &mut HashSet<Rc<i32>>) { 
    if unprocessed.contains(v) { 
     unprocessed.remove(v); 
    } 
} 

fn herp(v: Rc<i32>) { 
    let mut unprocessed: HashSet<Rc<i32>> = HashSet::new(); 
    unprocessed.insert(v); 

    while let Some(u) = unprocessed.iter().cloned().next() { 
     // If you don't use u afterwards, 
     // you could also pass if by value to derp. 
     derp(&u, &mut unprocessed); 
    } 
} 

fn main() { 
    println!("Hello, world!"); 
    herp(Rc::new(10)); 
} 
+0

Si le type n'est pas copiable, il pourrait être plus facile de laisser la boucle consommer l'ensemble et laisser 'derp' insérer les éléments dans un nouveau' HashSet' –

+0

True, mais cela ne serait pas t travailler si 'derp' utilise l'ensemble à d'autres fins que de supprimer l'élément courant, par ex. si le traitement d'un élément dépend de ce que d'autres éléments sont à traiter (l'implémentation de 'derp' dans la question est juste à des fins de démonstration, si je comprends bien). –

7

Vous devez penser à ce que HashSet est réellement. Le IterMut que vous obtenez de HashMap::iter_mut() est seulement mutable de la part de la valeur: (&key, &mut val), ((&'a K, &'a mut V))

HashSet est essentiellement un HashMap<T,()>, de sorte que les valeurs réelles sont les clés, et si vous modifiez les clés du hachage d'entre eux doit être mis à jour ou vous obtenez un invalide HashMap.

+0

Comment suggéreriez-vous de remplacer un 'HashSet' avec une autre collection. J'ai juste besoin de 'contains',' remove' et 'insert'. Un vecteur avec un breuvage maison «contient»? Et un moyen de «supprimer» un objet spécifique, soit en recherchant son index ou une autre approche. –

+0

@FilipAllberg Pouvez-vous donner un échantillon de code de ce que vous essayez réellement de faire? – Arjan

+0

Fourni un exemple condensé dans le message original –