The authoritative guide to Rust in 5 minutes (35) status mode

The authoritative guide to Rust in 5 minutes (35) status mode

Implement state mode

This section takes the state pattern in the object-oriented design pattern as an example, and uses rust to realize the following functions:

  1. Generate a blank draft document.
  2. After the draft is completed, request approval for this draft status article.
  3. After the article is approved, it will be officially released.
  4. Only articles that have been successfully published are returned, and articles that have not passed approval cannot be published.

Expected effect

Let's take a look at the final usage:

//post/src/main.rs use post::Post; let mut post = Post::new(); post.add_text( "I ate salad at noon" ); println! ( " Writing : {}" , post.content()); // Writing : "" post.request_review(); println! ( "Approval: {}" , post.content()); //Approval: "" post.approve(); ! println ( "Released: {}" , post.content ()); //Released: "At noon I ate a salad" Copy the code

First define the post structure Post:

//post/src/lib.rs pub struct Post { //The state object corresponding to the article, draft, under approval, and published respectively correspond to different state objects //As for why the Option type is used, the state: Option < Box will be explained later < dyn State>>, //the content of the article content: String , } Copy code

Defining the State trait is equivalent to defining a state interface, requiring each article state to implement the methods in the state interface:

trait State { //Request the approval method, pay attention to the need to obtain self ownership in the signature, and return the seat to a new state //When using the method, the caller is the Box smart pointer, for example: post.state.request_review() //So request_review In the parameters, self needs to be signed Box<Self> fn request_review ( self : Box < Self >) -> Box < dyn State>; //Publish method, also need to obtain the right of acquisition, and return to the new state fn approve ( self : Box < Self >) -> Box < dyn State>; //Get the content of the article without acquiring ownership, pass in the post object, and return its content to fn content < 'a >(& self , post: & 'a Post) -> & 'a str ; } Copy code

Definition draft status:

// struct Draft {} //Implement State state impl State for Draft for Draft { //Implement request approval method, where self acquires the ownership of the original state, which means that the original state is not available fn request_review ( self : Box < Self >) -> Box < dyn State> { //The status of the draft is converted to the state of approval, here returns the state of approval Box ::new(PendingReview {}) } //Implement the publishing method, and also get the original state fn approve ( self : Box < Self >) -> Box < dyn State> { //Since the draft state cannot be published directly, returning self here means calling invalid self } //Implement the method of obtaining content fn content < 'a >(& self , _post: & 'a Post) -> & 'a str { //The content cannot be obtained in the draft state, so the empty string "" is returned here } } Copy code

Define the approval status:

//Structure struct PendingReview {} //Implement State for PendingReview impl State for PendingReview { //Request for approval fn request_review ( self : Box < Self >) -> Box < dyn State> { //Since it is currently in the approval state, return to self self } //Publish fn approve ( self : Box < Self >) -> Box < dyn State> { //The approval state is converted to the published state, here returns the published state Box ::new(Published {}) } //Get the content fn content < 'a >(& self , _post: & 'a Post) -> & 'a str { //Cannot get the content during approval, return the control string "" } } Copy code

Define the release status:

//Published structure struct Published {} //Implement State for Published impl State for Published { //Request for approval fn request_review ( self : Box < Self >) -> Box < dyn State> { //Cannot request approval for published, so return to self } //Publish fn approve ( self : Box < Self >) -> Box < dyn State> { //The published state cannot be published again, so return to self } //Get content fn content < 'a >(& self , post: & 'a Post) -> & 'a str { //Post status needs to return the content of the article post.content.as_str() } } Copy code

The method to realize the post conversion state:

impl Post { //Create a new post structure pub fn new () -> Self { Post { state: Some ( Box ::new(Draft ())), //initialized to the draft state content: String ::new(), //content is empty } } //Add text pub fn add_text (& mut self , text: & str ) { self .content.push_str(text) } //Get the content of the article pub fn content (& self ) -> & str { //Take the state out of Option::Some, because there is no need to take ownership, so here is a reference if let Some (state) = & self .state { //Pass the current article reference to the state object return state.content(& self ) } else { "" } //A simpler approach: //as_ref method can take out the reference of Some content and return the Result type, //then use unwrap to get the reference to the state object self .state.as_ref().unwrap().content(& self ) } //Request approval and need to change the state, so here is a variable reference pub fn request_review (& mut self ) { //Since Rust does not allow null values, if you use if let Some(state) = self.state //will Taking ownership of self.state, resulting in self.state being empty, //But we need the ownership of state in the signature of the state.request_review method. //So, here use the take method of Option<T> to take out the Some value of the state field //and leave a None in the original position. Doing so allows us to move the value of state from Post, //and finally take ownership of state. if let Some (state) = self .state.take() { //Convert to the state under approval self .state = Some (state.request_review()) } } //To publish an article, you also need to change the state pub fn approve (& mut self ) { //Same as requesting to publish a state transition, you need to obtain the ownership of the state if let Some (state) = self .state.take() { //Transition for publishing state Self .STATE = s Some (state.approve ()) } } } Copy code

So far, it is convenient to use the state mode to implement the publishing process of the article.

Optimized implementation

The object-oriented state mode is used above. When adding another state in the future, you only need to implement another state, which is very convenient, but there are many repetitions in the code, such as the request_review and approve methods in each state. There are also request_review and approve methods in the Post structure.


It is naturally feasible to implement a state model strictly in accordance with the definition of an object-oriented language, but this does not give full play to the power of Rust. The following will modify part of the code to enable the post library to expose invalid states and state transitions as compile-time errors.


Realize state transition as a transition between different types:

//post/src/lib.rs //define the post structure pub struct Post { content: String } impl Post { //The initial state of creating an article is the draft type pub fn new () -> DraftPost { DraftPost { content: String ::new() } } //Get the content of the article pub fn content (& self ) -> & str { self .content.as_str() } } //Define the draft structure pub struct DraftPost { content: String } impl DraftPost { //Only the draft supports adding text pub fn add_text (& mut self , text: & str ) { self .content.push_str(text) } //You can initiate an approval for the draft and return to the status of approval pub fn request_review ( self ) -> PendingReviewPost { PendingReviewPost { content: self .content } } } //Define the structure pub struct PendingReviewPost in approval { content: String } impl PendingReviewPost { //The final article will be returned to the final post after the article is approved pub fn approve ( self ) -> Post { Post { content: self .content } } } Copy code

Use it again:

//post/src/main.rs use post::Post; //Get the draft let mut draft = Post::new(); draft.add_text( "hello," ); draft.add_text( "world" ); //Draft approval let pending_review_post = draft.request_review(); //Draft approval is passed and an official post is obtained let post = pending_review_post.approve(); println! ( "{}" , post.content()); //hello, worldcopy code

After the above optimization, some methods that should not belong to a certain state, such as the publishing method of the draft, the method of obtaining content, etc., are removed, and users will no longer be able to call

draft.content()
,
draft.approve()
Waiting for some meaningless methods, the overall code is more streamlined.

Cover image: Follow Tina to draw America

Pay attention to the official account of "Ma Sheng Bi Tan" to read more latest chapters