Rouille vs. Etat

Important : pour une lecture confortable de l'article, vous devez être capable de lire le code source dans Rust et comprendre pourquoi tout encapsuler Rc<RefCell<...>>est mauvais.



introduction



Rust n'est généralement pas considéré comme un langage orienté objet: il n'y a pas d'héritage d'implémentation; à première vue, il n'y a pas non plus d'encapsulation; Enfin, les graphiques de dépendance des objets mutables si familiers aux adeptes de la POO semblent aussi laids que possible (il suffit de regarder tous ceux Rc<RefCell<...>>- ci et Arc<Mutex<...>>!)



Certes, l'héritage d'implémentation est considéré comme nuisible depuis plusieurs années, et les gourous de la POO disent des choses très correctes comme "un bon objet est un objet immuable". Alors je me demandais: dans quelle mesure la réflexion sur les objets et la rouille s'accordent-elles vraiment ?



Le premier cobaye sera le modèle d'État, dont la mise en œuvre pure fait l'objet de cet article.



Il a été choisi pour une raison: un chapitre de The Rust Book est consacré au même modèle . Le but de ce chapitre était de montrer que seuls les mauvais garçons et filles écrivent du code orienté objet dans Rust: ici, vous Optiondevez copier et coller des implémentations de méthodes inutiles et triviales dans toutes les implémentations du trait. Mais si vous appliquez quelques astuces, tout le passe-partout disparaîtra et la lisibilité augmentera.



Échelle de travail



L'article original a modélisé le flux de travail d'un article de blog. Montrons notre imagination et adaptons la description originale aux dures réalités russes:



  1. Tout article sur Habré était autrefois un projet vide, que l'auteur devait remplir de contenu.
  2. Lorsque l'article est prêt, il est envoyé pour modération.
  3. Dès que le modérateur approuve l'article, il est publié sur Habré.
  4. Tant que l'article n'est pas publié, les utilisateurs ne doivent pas voir son contenu.


Toute action illégale avec l'article ne doit avoir aucun effet (par exemple, vous ne pouvez pas publier un article non approuvé à partir du bac à sable).



La liste ci-dessous montre le code correspondant à la description ci-dessus.



// main.rs

use article::Article;

mod article;

fn main() {
    let mut article = Article::empty();

    article.add_text("Rust    -");
    assert_eq!(None, article.content());

    article.send_to_moderators();
    assert_eq!(None, article.content());

    article.publish();
    assert_eq!(Some("Rust    -"), article.content());
}


Article jusqu'ici ressemble à ceci:



// article/mod.rs

pub struct Article;

impl Article {
    pub fn empty() -> Self {
        Self
    }

    pub fn add_text(&self, _text: &str) {
        // no-op
    }

    pub fn content(&self) -> Option<&str> {
        None
    }

    pub fn send_to_moderators(&self) {
        // no-op
    }

    pub fn publish(&self) {
        // no-op
    }
}


Cela passe par toutes les affirmations sauf la dernière. Pas mal!



Mise en œuvre du modèle



Ajoutons un trait vide State, un état Draftet quelques champs à Article:



// article/state.rs

pub trait State {
    // empty
}

// article/states.rs

use super::state::State;

pub struct Draft;

impl State for Draft {
    // nothing
}

// article/mod.rs

use state::State;
use states::Draft;

mod state;
mod states;

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

impl Article {
    pub fn empty() -> Self {
        Self {
            state: Box::new(Draft),
            content: String::new(),
        }
    }

    // ...
}


Problèmes avec partir conception



State, . , - :



trait State {
    fn send_to_moderators(&mut self) -> &dyn State;
}


, , , — .



?



pub trait State {
    fn send_to_moderators(&mut self) -> Box<dyn State>;
}


. . , ?



:



pub trait State {
    fn send_to_moderators(self: Box<Self>) -> Box<dyn State>;
}


: ( self). , Self: Sized, .. . trait object, .. .





: , , , . , , ; , .



P.S.: Amethyst.



use crate::article::Article;

pub trait State {
    fn send_to_moderators(&mut self) -> Transit {
        Transit(None)
    }
}

pub struct Transit(pub Option<Box<dyn State>>);

impl Transit {
    pub fn to(state: impl State + 'static) -> Self {
        Self(Some(Box::new(state)))
    }

    pub fn apply(self, article: &mut Article) -> Option<()> {
        article.state = self.0?;
        Some(())
    }
}


, , Draft:



// article/states.rs

use super::state::{State, Transit};

pub struct Draft;

impl State for Draft {
    fn send_to_moderators(&mut self) -> Transit {
        Transit::to(PendingReview)
    }
}

pub struct PendingReview;

impl State for PendingReview {
    // nothing
}

// article/mod.rs

impl Article {
    // ...
    pub fn send_to_moderators(&mut self) {
        self.state.send_to_moderators().apply(self);
    }
    // ...
}


-



: Published, State, publish PendingReview. Article::publish :)



. content State, Published , , Article:



// article/mod.rs

impl Article {
    // ...
    pub fn content(&self) -> Option<&str> {
        self.state.content(self)
    }
    // ...
}

// article/state.rs

pub trait State {
    // ...
    fn content<'a>(&self, _article: &'a Article) -> Option<&'a str> {
        None
    }
}

// article/states.rs

impl State for Published {
    fn content<'a>(&self, article: &'a Article) -> Option<&'a str> {
        Some(&article.content)
    }
}


, ? , !



impl Article {
    // ...
    pub fn add_text(&mut self, text: &str) {
        self.content.push_str(text);
    }
    // ...
}


( ) , .



! !



, Article , - , , . ? , ! .





.



, - Rust , , . - -.



, , Rust . , Observer: , Arc<Mutex<...>>!



, .




All Articles