2017-03-05 4 views
0

Je veux prendre une liste chaînée et la peupler avec des instances d'une structure, mais seulement si la liste ne contient pas déjà l'élément que je songe à ajouter.Quelle est la meilleure pratique avec le vérificateur d'emprunt pour inspecter les références mutables?

Je travaille avec des points donc si (3,5) est dans la liste, je ne veux pas ajouter, sinon que je fais.

Mon code actuel:

use std::collections::LinkedList; 

struct Location { 
    x: i32, 
    y: i32, 
} 

fn main() { 
    let mut locations = LinkedList::new(); 

    loop { 
     let location_set = &mut locations; 
     // Scanner stuff happens. 
     if !has_location(location_set, &next_checkpoint_x, &next_checkpoint_y) { 
      let point = Location { 
       x: next_checkpoint_x, 
       y: next_checkpoint_y, 
      }; 
      locations.push_back(point); 
     } 
    } 
} 

fn has_location(location_list: LinkedList<Location>, target_x: &i32, target_y: &i32) -> bool { 
    true // Just until I can figure out this mutability stuff 
} 

Je suis en mesure de le faire fonctionner par ces changements, mais cela me semble mal à.

loop { 
    if !has_location(&mut locations, &next_checkpoint_x, &next_checkpoint_y) { 
     // stuff 
    } 
} 
fn has_location(location_list: &LinkedList<Location>, target_x: &i32, target_y: &i32) -> bool { 
    true 
} 

Je ne veux pas has_location pour pouvoir muter la liste chaînée, je veux juste qu'il ce soit en mesure d'emprunter afin de pouvoir regarder à l'intérieur de celui-ci. Je ne veux pas avoir à penser à has_location (ou une fonction similaire) affectant les listes liées qu'il inspecte. C'est pourquoi j'ai créé le location_set. Je veux quelque chose de se référer à des endroits dans un sens de lecture seule et que pour transmettre à la fonction has_location et pour ce qui est appelé (lieux) de ne pas être détruit après avoir appelé la fonction has_location. J'inclus le & dans le passage des paramètres dans l'appel de la fonction, parce que je ne veux pas que les paramètres passés soient détruits - donc je les veux empruntés?

Est-ce que je suis vouloir quelque chose qui fait sens - si je déclare à l'origine des endroits comme une liste chaînée mutable puis-je passer une version immuable de celui-ci à une fonction d'évaluer?

Répondre

2

Si vous avez un emprunt mutable, vous pouvez toujours l'emprunter comme un emprunt immuable reborrow. Cela arrive même implicitement (un &mut T va contraindre à &T). Par conséquent, vous pouvez simplement passer location_set directement à has_location. Ou si vous voulez préciser que la fonction ne mute pas son argument, vous pouvez écrire &*location_set au lieu de location_set (bien que je trouve cela inutile).

Notez également que vous ne pouvez pas utiliser l'emprunt mutable alors que l'emprunt immuable existe; l'emprunt immuable gèle la structure des données alors qu'il est dans la portée. De même, vous ne pouvez pas utiliser une variable alors qu'il y a un emprunt mutable sur cette variable dans la portée. Dans votre premier exemple de code, vous ne pouvez pas référence à locations en location_set est portée, parce que location_set prend ralonge mutable sur locations, mais vous pouvez simplement utiliser à nouveau location_set, car push_back ne nécessite qu'un emprunt mutable (il ne prend pas LinkedList en valeur).

Une fonction qui inspecte seulement une structure de données recevra généralement un emprunt immuable à la structure de données. Si la structure de données était transmise par valeur à la place, la fonction en prendrait la propriété et la détruirait donc avant de revenir (à moins qu'elle ne soit déplacée ailleurs). Par conséquent, oui, vous voulez has_location pour accepter un emprunt immuable au LinkedList. En acceptant un emprunt immuable (par opposition à un emprunt mutable), le compilateur vous empêchera de modifier le LinkedList (sauf si vous utilisez le code dangereux).

Mettre le tout ensemble:

use std::collections::LinkedList; 

struct Location { 
    x: i32, 
    y: i32, 
} 

fn main() { 
    let mut locations = LinkedList::new(); 
    let next_checkpoint_x = 0; 
    let next_checkpoint_y = 0; 

    loop { 
     let location_set = &mut locations; 
     // Scanner stuff happens. 
     if !has_location(location_set, &next_checkpoint_x, &next_checkpoint_y) { 
      let point = Location { x: next_checkpoint_x, y: next_checkpoint_y }; 
      location_set.push_back(point); 
     } 
    } 
} 

fn has_location(location_list: &LinkedList<Location>, target_x: &i32, target_y: &i32) -> bool { 
    true 
} 

Quelque chose que je ne comprends pas bien est dans votre exemple, location_set est passé à has_location directement (si la fonction est propriétaire, non?). Dans mon esprit signifie que, à la fin de has_location portée, location_set devrait être détruit, non? Comment location_set continue d'exister pour être utilisé dans le bloc if?

Non, has_location ne possède pas location_set. S'il s'agissait d'un autre type qui n'implémente pas Copy (par exemple String), alors vous auriez raison, mais les références ont des règles spéciales pour les rendre plus pratiques à utiliser.

Lorsque vous passez une référence à une fonction, le compilateur automatiquement réemprunter que référence pour produire une nouvelle référence, généralement avec une durée de vie plus courte. Ici, le compilateur ré-exécute la référence mutable et produit une référence immuable; la référence mutable ne peut pas être utilisée tant que la référence immuable n'est pas hors de portée (la référence immuable n'est pas liée à une variable ici, donc vous ne le remarquez pas vraiment). Conceptuellement, c'est comme si vous passiez une référence immuable à la référence mutable (dans Rust, un & &mut T ne vous permet pas de muter le T, car il pourrait y avoir plusieurs copies de cette référence externe), c'est juste que les deux références sont " aplati ".

Aussi, si location_set est immuable, comment est push_back en mesure d'ajouter à la fin de la liste, est-ce parce que la fonction l'emprunt mutable contraint dans un immuable emprunt?

location_set est encore une référence mutable (parce que nous créons avec l'opérateur &mut). Le fait que has_location fonctionne sur une référence immuable ne change pas le fait que location_set est une référence mutable. Une fois que l'appel à has_location a été évalué, location_set peut être réutilisé en tant que référence mutable, ainsi les opérations de mutation telles que push_back sont autorisées. Rappelez-vous que la mutabilité est Rust est purement un concept de compilation; le mut ou l'absence de mut laisse simplement le compilateur valider que votre code ne fait pas d'opérations illégales, mais une fois que votre code est compilé, ces marqueurs ne sont pas visibles.

+0

Ceci est très utile merci. Quelque chose que je ne comprends pas est dans votre exemple, location_set est passé à has_location directement (donc la fonction le possède, non?). Dans mon esprit, cela signifie qu'à la fin de la portée de has_location, location_set devrait être détruit, non? Comment location_set continue d'exister pour être utilisé dans le bloc if? De plus, si location_set est immuable, comment push_back peut-il être ajouté à la fin de la liste, est-ce parce que la fonction contraint l'emprunt mutable à emprunter de manière immutable? Merci! – neogeek23

+0

Voir ma réponse à la fin de la réponse. –