Reputation: 414
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
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 ofself
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