Chapter 17.3 multiple approvals with type system

Hello everyone!

I'm going through the Rust book. I want to implement the exercises given in the section Trade-offs of the State Pattern:

  • Add a reject method that changes the post’s state from PendingReview back to Draft .
  • Require two calls to approve before the state can be changed to Published .
  • Allow users to add text content only when a post is in the Draft state. Hint: have the state object responsible for what might change about the content but not responsible for modifying the Post .

However I want to implement them into the version that uses different types instead of Object Traits. I'm blocking on the second exercise regarding the double approvals. Here is my attempt:

pub struct Post {
  content: String,
}

pub struct PendingReviewPost {
  content: String,
  approvals: u32,
}

impl PendingReviewPost {
  pub fn approve(mut self) -> Post {
    // I intentionally left out the creation of PendingReviewPost in the DraftPost.request_review() function
    // there I initialize approvals to 0
    self.approvals += 1;
    
    if self.approvals < 2 {
       // don't know what to actually return here
    }

    Post {
      content: self.content,
    }
  }

In this chapter we intentionally take ownership of the self parameter in the approve function on PendingReviewPost so we can return a Post instance and the PendingReviewPost doesn't exist anymore. The thing is we should call the function twice for it to be approved but since the first call we took the ownership of self we won't be able to call it again in the main function. I tried several options but didn't succeed:

  1. Return an Option<Post> and in the if just return None, we'd have also to make &mut self to not take ownership. This would work but I don't know how to take ownership of it afterwards once we reached the desired approvals count. If we don't then PendingReviewPost and Post instances we'll both exist in the main function. Should I copy the content and call drop() on self before returning the Post :thinking: ?
  2. Return Result<Post, PendingReviewPost> but this felt wrong.
  3. Keep the ownership of self but then I'd need to return a new instance of PendingReviewPost but that would violate the return type since we can't return both Post and PendingReviewPost from the function.
  4. Add another function that would just increment the approvals count and keep approve to actually return the new Post instance but it changes the API and I didn't think it was the purpose of this exercise.

It feels more complicated than I think it should be. What am I missing here?

Thank you for your help.

My suggestion is to remove Chapter 17 from the book and make it clear to the newcomers that OOP is not the way to go. And not only in rust, some proudly OO languages discourages its use and call for Composition over Inheritance.

My suggestion for the task at hand: Best way is to encode the states in an enum. You don't need a struct for every different state.

The former addresses the latter -- you could use Either or your own type instead of Result specifically. Enums are another possibility.

However, after an admittedly brief skim, I think the idea that they are getting at is that you add another type, so that you have PendingTwoReviewsPost and PendingOneReviewPost or the like, and approving the former returns the latter, and approving the latter returns a Post. I.e., encoding the state into the types.

Thanks for the suggestion and it seems indeep that OOP is not the way to go in Rust.

I didn't know about the Either type, thank you for the suggestion.

Yup your last suggestion seems like what they intended indeed.

Thank you.