Hanzy
Hanzy

Reputation: 414

Rust Lifetime mismatch in trait method

I'm working through the Rust book, and trying to implement logic to allow text to only be added to a blog Post if its in the Draft state, as can be found here (one of the suggested exercises).

The idea is to implement the state pattern in Rust using structs and traits. I simply want to pass a string slice to a default implementation of add_text which returns an empty string slice if not in the Draft state. Then I'll override the default implementation for the Draft state and return the string slice that was passed in for the text in the Draft state.

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) {
        let text = self.state.as_ref().unwrap().add_text(text);
        // self.state.as_ref().unwrap().add_content(text)
        self.content.push_str(text);
    }

    //snip

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 reject(self: Box<Self>) -> Box<dyn State>;
    fn add_text(&self, text: &str) -> &str {
        ""
    }
}

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 // don't want to approve a draft before review!
    }
    fn reject(self: Box<Self>) -> Box<dyn State> {
        self // reject doesn't do anything on a draft!
    }
    fn add_text(&self, text: &str) -> &str {
        text
    }
}

I'm getting a lifetime mismatch on the very last method above add_text inside impl State for Draft. The message reads:

lifetime mismatch

...but data from `text` is returned hererustc(E0623)
lib.rs(67, 30): this parameter and the return type are declared with different lifetimes...
lib.rs(67, 39):
lib.rs(68, 9): ...but data from `text` is returned here

I'm new to Rust and can't get the lifetime annotations right in this case. I tried explicit lifetime annotations but it didn't help. Also, I know that since one of the references is &self all lifetime parameters should have the same lifetime as &self automatically (I think?).

Can someone enlighten me on getting this code to compile? It also may be useful to those using the book in the future.

Upvotes: 2

Views: 2204

Answers (1)

edwardw
edwardw

Reputation: 14032

You were tripped over by lifetime elision rules:

Each elided lifetime in input position becomes a distinct lifetime parameter.

If there is exactly one input lifetime position (elided or not), that lifetime is assigned to all elided output lifetimes.

If there are multiple input lifetime positions, but one of them is &self or &mut self, the lifetime of self is assigned to all elided output lifetimes.

Otherwise, it is an error to elide an output lifetime.

In your code fn add_text(&self, text: &str) -> &str, the returned &str picks up the elided lifetime from &self, but then it is actually the second parameter. A mismatch as if it were:

fn add_text<'a, 'b>(&'a self, text: &'b str) -> &'a str {
    text
}

The trick is to make lifetime explicit here:

trait State {
    fn add_text<'a>(&'a self, text: &'a str) -> &'a str;
}

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

If you want a slightly more general version:

trait State {
    fn add_text<'a, 'b: 'a>(&'a self, text: &'b str) -> &'a str;
}

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

Here it says as long as text outlives &self all is good. You decide if the extra generic lifetime is worth the trouble.

Upvotes: 4

Related Questions