4
votes

While working through the OOP-Chapter of the Rust book (2nd Edition) I took on the optional task to implement the method add_text for the following struct

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

There are three structs that implement the State trait, but only the Draft struct should actually do something. I implemented this as follows

trait State {
    // snip
    fn add_text(&self, post: &mut Post, text: &str) { }
}


struct Draft { }

impl State for Draft {
    // snip
    fn add_text(&self, post: &mut Post, text: &str) {
        post.content.push_str(text);
    }
}

My problem is that in order to get the State from my post struct to call the add_text method I immutably borrow self (in Post) and can not pass on a mutable reference to the add_text method of the State trait:

impl Post {
    // snip

    pub fn add_text(&mut self, text: &str){
        let state = self.state.as_ref().unwrap();  // This immutably borrows self
        state.add_text(self, text);  // so that this mutable borrow is no longer possible
    }
}

How do I deal with this dilemma? I definitely need a mutable reference to the Post, otherwise I can't change its text. On the other hand, I need to get the State first since otherwise I can not even call the method.

One way to work around this would be be to change add_text to get_text_to_add which would not require mutability of the Post, but I would like to make sure that I am not overseeing any options to solve this.

1
This to me looks like a code smell. First, the State::add_text method impl on Draft has nothing to do with the Draft struct whatsoever - you might as well remove the &self parameter and be done with it. Second, as an external consumer of this hypothetical library I'd expect the add_text method to be on the Post struct itself. You would be much better off moving add_text to Post, and looking up information or mutating the state field as and when needed. If you only want to add_text if the post is a draft, you can check if self.state is a Draft inside Post::add_text.EvilTak

1 Answers

2
votes

With structs Rust is smart enough to be able to do disjoint borrows so you don't need to pass a mutable reference to the entire Post struct, just the part of it you need to modify (in this case content).

trait State {
    // snip

    // Modify the method on the add_text trait so that it
    // takes a mutable reference to String
    fn add_text(&self, content: &mut String, text: &str) { }
}

struct Draft { }

impl State for Draft {
    // snip

    // Update the implementation of State for Draft so that it
    // matches the new signature
    fn add_text(&self, content: &mut String, text: &str) {
        content.push_str(text);
    }
}

impl Post {
    // snip

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

        // Now when you call add_text you don't require a mutable 
        // reference to self, just to self.content and so the 
        // borrow checker is happy
        state.add_text(&mut self.content, text);  
    }
}

This should work but it feels a bit forced, (as EvilTak points out the reference to self in Draft::add_text is redundant). I guess that's part of the point of the exercise though; while it is possible to implement certain patterns from OOP in Rust there are better ways to model the problem.