2017-08-31 6 views
2

J'ai un dépôt Git caché derrière un Mutex:Comment puis-je remplacer la valeur dans un Mutex?

pub struct GitRepo { 
    contents: Mutex<GitContents>, 
    workdir: PathBuf, 
} 

Je veux l'interroger, mais seulement un maximum de fois: après qu'il a été interrogé, je veux simplement utiliser les résultats que nous avons obtenu la première fois. Un référentiel a un git2::Repository ou un vecteur de résultats. Un Repository est Send mais pas Sync.

enum GitContents { 
    Before { repo: git2::Repository }, 
    After { statuses: Git }, 
} 

struct Git { 
    statuses: Vec<(PathBuf, git2::Status)>, 
} 

Le GitContents ENUM reflète le fait que nous avons soit le dépôt d'une requête, ou les résultats de l'interrogation, mais jamais les deux.

J'essaie d'obtenir Rust pour faire respecter cette propriété en ayant la fonction qui transforme un référentiel en états consomment le référentiel tel qu'il produit le vecteur d'état:

fn repo_to_statuses(repo: git2::Repository, workdir: &Path) -> Git { 
    // Assume this does something useful... 
    Git { statuses: Vec::new() } 
} 

Cependant, je ne peux pas obtenir le Mutex pour jouer sympa avec cela. Voici ma tentative à ce jour pour écrire une fonction qui interroge un GitRepo avec un prédicat P, en remplaçant la valeur à l'intérieur du Mutex si elle n'a pas été encore interrogé:

impl GitRepo { 
    fn search<P: Fn(&Git) -> bool>(&self, p: P) -> bool { 
     use std::mem::replace; 
     // Make this thread wait until the mutex becomes available. 
     // If it's locked, it's because another thread is running repo_to_statuses 
     let mut contents = self.contents.lock().unwrap(); 
     match *contents { 
      // If the repository has been queried then just use the existing results 
      GitContents::After { ref statuses } => p(statuses), 
      // If it hasn't, then replace it with some results, then use them. 
      GitContents::Before { ref repo } => { 
       let statuses = repo_to_statuses(*repo, &self.workdir); 
       let result = p(&statuses); 
       replace(&mut *contents, GitContents::After { statuses }); 
       result 
      }, 
     } 
    } 
} 

Bien qu'il y ait mutation en cause, cette méthode ne prend &self plutôt que &mut self car il renvoie le même résultat, que le référentiel soit interrogé pour la première ou la deuxième fois, même si le premier travail est en cours. Mais Rust se plaint:

  • Il refuse de déplacer le repo sur le contenu que j'ai emprunté à repo_to_statuses(*repo, &self.workdir), même si je sais que la valeur doit se remplacer immédiatement après. ("ne peut pas sortir du contenu emprunté")
  • Il ne m'aime pas replace -ing &mut *contents soit, parce que j'emprunte le contenu immuablement que la valeur étant match -ed. ("ne peut pas emprunter le contenu" comme mutable parce qu'il est aussi emprunté comme immuable ")

Y a-t-il un moyen de convaincre le vérificateur d'emprunts de mes intentions?

Répondre

3

La question que vous posez et le vrai problème interne n'ont rien à voir avec un Mutex, une fois que vous l'avez verrouillé et que vous avez une référence mutable ou un type qui implémente DerefMut.

Vous pouvez affecter une nouvelle valeur à la référence à l'aide de l'opérateur de déréférencement *. Si vous avez besoin de la valeur précédente, vous pouvez utiliser std::mem::replace.

use std::sync::Mutex; 
use std::mem; 

fn example_not_using_old_value(state: &Mutex<String>) { 
    let mut state = state.lock().expect("Could not lock mutex"); 
    *state = String::from("dereferenced"); 
} 

fn example_using_old_value(state: &Mutex<String>) -> String { 
    let mut state = state.lock().expect("Could not lock mutex"); 
    mem::replace(&mut *state, String::from("replaced")) 
} 

fn main() { 
    let state = Mutex::new("original".into()); 
    example_not_using_old_value(&state); 
    let was = example_using_old_value(&state); 

    println!("Is now {:?}", state); 
    println!("Was {:?}", was); 
}  

Nous déréférencer le MutexGuard<T> pour obtenir un T et prendre une référence mutable à cela, ce qui donne un &mut T que nous pouvons appeler mem::replace avec.


Votre problème est plus large parce que vous ne pouvez pas sortir du contenu emprunté (voir le numerous Q&A for that).Voir ces Q & directement concerné A:

Vous pouvez ajouter une nouvelle variante de ENUM qui représente l'état où tout a été déménagé, mais rien n'a été déplacé dans encore. Ensuite, vous pouvez remplacer votre valeur par cette variable et prendre possession de l'ancienne valeur, effectuer vos opérations, puis redéfinir la nouvelle valeur.

+0

"Vous souhaiterez peut-être ajouter une nouvelle variante enum représentant l'état lorsque tout a été déplacé mais que rien n'a encore été déplacé." C'était la partie que j'avais besoin d'entendre. Je vous remercie! –