Hello. I'm quite new to Rust. I was experimenting with Rust and found this issue where method and non-method result in different in lifetime. I have an assumption on why it work the way it does, but I'm not so sure about it. Please correct me if I'm mistaken, or please elaborate further.
I guess the compiler always assume lifetime 'a of the struct to everything that is borrowed from &self parameter. Resulting in the return type of new_from_self be Container<'a, Item>, which causes problem when I assign it to self.other which mutates self. The compiler thinks I'm keeping immutable reference while mutating the object, which is not allowed.
struct Container<'a, Item> {
item: &'a Item,
other: Option<Box<Container<'a, Item>>>,
}
impl<'a, Item> Container<'a, Item> {
fn new_from_self(&self) -> Container<Item> {
Container {
item: self.item,
other: None,
}
}
fn new_from_self_with_lifetime_bound<'b>(&self) -> Container<'b, Item>
where
'a: 'b,
{
Container {
item: self.item,
other: None,
}
}
fn new_from_non_method(other: &Self) -> Self {
Container {
item: other.item,
other: None,
}
}
fn item_to_other(&mut self) {
// This doesn't work, got E0506 because I'm assigning it to self.other,
let new_container = self.new_from_self();
self.other = Some(Box::new(new_container));
// this does work
// let new_container = self.new_from_self_with_lifetime_bound();
// self.other = Some(Box::new(new_container));
// this does work
// I assume the lifetime got resolved into something similar to new_from_self_with_lifetime_bound
// let new_container = Self::new_from_non_method(self);
// self.other = Some(Box::new(new_container));
}
}
Lifetimes can't change underneath a &mut (we say they are invariant), so you need to create a Option<Box<Container<'a, Item>>> here. Looking at the signature on new_from_self, you would have to reborrow *self for 'a. But the reference you have to *self doesn't last that long; it only lasts for 's.
new_from_non_method works because the Container<'a, Item> lifetime is the same in the output as in the input due to the Self alias. The reborrow of *self can be arbitrarily short when calling it.
new_from_self_with_lifetime_bound works the same as new_from_non_method (aside from being a method or not) when 'b is equal to 'a. So writing it the following ways also works:
I recommend using #![deny(elided_lifetimes_in_paths)] to make it more obvious when you're returning a borrowed lifetime. It makes things more clear once you've absorbed the elision rules. âŠī¸
Oh, one more thing. Sometimes the compiler gives bad advice, and this scenario is one of those. With the signature written like so, the compiler says:
error: lifetime may not live long enough
--> src/lib.rs:32:33
|
8 | impl<'a, Item> Container<'a, Item> {
| -- lifetime `'a` defined here
...
30 | fn item_to_other<'s>(self: &'s mut Container<'a, Item>) {
| -- lifetime `'s` defined here
31 | // This doesn't work, got E0506 because I'm assigning it to self.other,
32 | let new_container = self.new_from_self();
| ^^^^^^^^^^^^^^^^^^^^ argument requires that `'s` must outlive `'a`
|
= help: consider adding the following bound: `'s: 'a`
And when you have a &'s [...] Thing<'a>, it is implied that 'a: 's. The compiler has suggested that we add 's: 'a. Taken together, this means 's must equal 'a, so the net result of applying the help would be that our signature is the same as:
This is an anti-pattern because it requires borrowing the Container<'a, Item>exclusively for the entire rest of it's existence ('a). Once you do this by calling the method, you cannot use the Container directly ever again -- you can't call any more methods, and you can't even move it.
So a &'a mut Thing<'a> is pretty much never what you want. It can be easy to accidentally wind up with it when you write things like so:
// Self is `Container<'a, Item>`
fn item_to_other(&'a mut self) {