1
votes

La rouille orientée objet (The Rust Book Chapter 17 Blog)

Je souhaite implémenter le dernier point sur le options pour aller plus loin pour créer le blog à partir de Le langage de programmation Rust :

Autoriser les utilisateurs à ajouter du contenu textuel uniquement lorsqu'un message est à l'état Brouillon . Astuce: confiez à l'objet state la responsabilité de ce qui pourrait changer sur le contenu mais pas la responsabilité de la modification de Post .

Je souhaite implémenter la méthode add_text qui aurait une implémentation par défaut qui ne fait rien dans le trait State mais qui pousserait la chaîne dans post.content si l'état était Draft:

self.state.as_ref().unwrap().add_text(self, string)

Mon problème concerne cette ligne de Post :: add_text

self.state.as_ref().unwrap().add_text(&mut self, string)

J'obtiens un impossible d'emprunter car mutable essayez de supprimer & mut.

Cependant:

pub struct Post {
    state: Option<Box<dyn State>>,
    content: String,
}

impl Post {
    pub fn new() -> Post {
        Post {
            state: Some(Box::new(Draft {})),
            content: String::new(),
        }
    }

    pub fn add_text(&mut self, string: &str) {
        self.state.as_ref().unwrap().add_text(&mut self, string)
    }

    pub fn content(&self) -> &str {
        self.state.as_ref().unwrap().content(&self)
    }
}

trait State {
    fn content<'a>(&self, post: &'a Post) -> &'a str {
        ""
    }
    fn add_text<'a>(&self, post: &'a mut Post, string: &str) {
        "";
    }
}

impl State for Draft {
    fn add_text<'a>(&self, post: &'a mut Post, string: &str) {
        &mut post.content.push_str(string);
    }
}

Me donne «ne peut pas emprunter * self comme mutable car il est également emprunté comme immuable».

Quelqu'un peut-il m'aider avec ça? Je ne sais pas si je vais procéder de la bonne manière.


3 commentaires

Il est difficile de répondre à votre question car elle n'inclut pas un exemple reproductible minimal . Nous ne pouvons pas dire quels caisses (et leurs versions), types, traits, champs, etc. sont présents dans le code. Il serait plus facile pour nous de vous aider si vous essayez de reproduire votre erreur sur le Rust Playground si possible, sinon dans un tout nouveau projet Cargo, modifiez votre question pour inclure les informations supplémentaires. Il existe des conseils MRE spécifiques à Rust que vous pouvez utiliser pour réduire votre code d'origine à publier ici. Merci!


Veuillez coller l'erreur exacte et complète que vous obtenez - cela nous aidera à comprendre quel est le problème afin que nous puissions vous aider au mieux. Parfois, essayer d'interpréter un message d'erreur est délicat et c'est en fait une partie différente du message d'erreur qui est importante.


Génial - je vérifierai ces outils la prochaine fois!


3 Réponses :


4
votes

Le problème est que state est membre de Post . Si vous empruntez état , vous ne pouvez pas emprunter mutuellement self (c'est-à-dire Post ) en même temps, et c'est vrai. Considérez ce qui se passerait si votre fonction Draft :: add_text () était assignée au post.state reçu :

impl Post {
    pub fn add_text(&mut self, string: &str) {
        let state = self.state.take();
        state.as_ref().unwrap().add_text(&mut self, string);
        assert!(self.state.is_none());
        self.state = Some(state);
    }
}

Ce serait faire disparaître self pendant que cette fonction est en cours d'exécution! Ce serait terriblement dangereux.

Ce modèle d'utilisation n'est pas autorisé dans Rust, mais plusieurs solutions de contournement sont disponibles. Le plus gentil, IMO, serait de séparer les données à muter en un autre type:

pub struct PostData {
    content: String,
}
pub struct Post {
    state: Option<Box<dyn State>>,
    data: PostData,
}
trait State {
    fn add_text<'a>(&self, post: &'a mut PostData, string: &str) {
        "";
    }
    //...
}
impl Post {
    pub fn add_text(&mut self, string: &str) {
        self.state.as_ref().unwrap().add_text(&mut self.data, string)
    }
}

Cela fonctionne parce que vous empruntez des morceaux séparés de self , c'est-à-dire self.state d'une part et self.data d'autre part.

Une autre solution de contournement simple serait de supprimer le statut de Publiez et rajoutez-le plus tard:

impl State for Draft {
    fn add_text<'a>(&self, post: &'a mut Post, string: &str) {
        post.state = None; //killed self!
    }
}

Personnellement, je trouve cette solution un peu hacky .


0 commentaires

1
votes

Une solution est fournie ici

Mais cette solution comporte quelques avertissements d'obsolescence. Voici ma version qui corrige les avertissements

Pour la postérité, voici le code dans le lien de l'aire de jeux fourni:

pub struct Post {
    state: Option<Box<dyn State>>,
    content: String,
}

impl Post {
    pub fn new() -> Post {
        Post {
            state: Some(Box::new(Draft {})),
            content: String::new(),
        }
    }

    pub fn add_text(&mut self, text: &str) {
        self.content = self.state.as_ref().unwrap().add_text(&self.content, text);
    }

    pub fn content(&self) -> &str {
        self.state.as_ref().unwrap().content(&self)
    }

    pub fn request_review(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.request_review())
        }
    }

    pub fn approve(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.approve())
        }
    }
}

trait State {
    fn request_review(self: Box<Self>) -> Box<dyn State>;
    fn approve(self: Box<Self>) -> Box<dyn State>;

    fn content<'a>(&self, _post: &'a Post) -> &'a str {
        ""
    }

    fn add_text(&self, original_text: &str, _text_to_add: &str) -> String {
        original_text.to_string()
    }
}

struct Draft {}

impl State for Draft {
    fn request_review(self: Box<Self>) -> Box<dyn State> {
        Box::new(PendingReview {})
    }

    fn approve(self: Box<Self>) -> Box<dyn State> {
        self
    }

    fn add_text(&self, original_text: &str, text_to_add: &str) -> String {
        format!("{}{}", original_text, text_to_add)
    }
}

struct PendingReview {}

impl State for PendingReview {
    fn request_review(self: Box<Self>) -> Box<dyn State> {
        self
    }

    fn approve(self: Box<Self>) -> Box<dyn State> {
        Box::new(Published {})
    }
}

struct Published {}

impl State for Published {
    fn request_review(self: Box<Self>) -> Box<dyn State> {
        self
    }

    fn approve(self: Box<Self>) -> Box<dyn State> {
        self
    }

    fn content<'a>(&self, post: &'a Post) -> &'a str {
        &post.content
    }
}


fn main() {
    let mut post = Post::new();

    post.add_text("I ate a salad for lunch today");
    post.add_text("\nAnd a steak!");
    assert_eq!("", post.content());

    post.request_review();
    post.add_text("\nAnd dessert!");
    assert_eq!("", post.content());

    post.approve();
    post.add_text("\nAnd coffee!");
    assert_eq!("I ate a salad for lunch today\nAnd a steak!", post.content());
}


0 commentaires

0
votes

Voici mon avis.

Puisqu'il a été laissé entendre que l'état serait responsable de ce qui pourrait changer et non de la modification du message, ajoutez add_text (ou un nom de fonction beaucoup plus approprié) au trait State. Faites-lui accepter une tranche de chaîne.

fn main() {
    let mut post = Post::new();

    post.add_text("I ate a salad for lunch today");
    post.add_text("\nThen slept");
    assert_eq!("", post.content());

    post.request_review();
    post.add_text("\nThen woke up");
    assert_eq!("", post.content());

    post.approve();
    post.add_text("\nThen got back to work");
    assert_eq!("I ate a salad for lunch today\nThen slept", post.content());
}

Implémentez la méthode add_text pour la structure Draft:

impl Post {
    // Some codes here
    pub fn add_text(&mut self, text: &str) {
        if let Some(s) = self.state.as_ref() {
            let text = s.add_text(text);
            self.content.push_str(text)
        }
    }
    // Some codes here
}

Réécrivez le add_text of Post struct de telle manière qu'il appellera add_text de l'état et retournera le "changement" qui devrait être ajouté au contenu actuel.

impl State for Draft {
    // Some codes here
    fn add_text<'a>(&self, text: &'a str) -> &'a str {
        text
    }
}

Seul l'état Brouillon sera renvoie la tranche de chaîne que nous lui passons. Nous utiliserons le add_text de l'état pour déterminer ce qu'il faut ajouter au contenu actuel.

trait State {
    // Some codes here
    fn add_text<'a>(&self, _text: &'a str) -> &'a str {
        ""
    }
}


0 commentaires