Lifetime problem: cannot return value referencing temporary value

I came across a question the discord channel

struct Node<'a> {
    parent: Option<&'a Node<'a>>
}

struct Genealogy<'a> (Vec<&'a Node<'a>>);

impl Genealogy<'_> {
    fn root(&self) -> &Node<'_> {
        self.0.last().unwrap()
    }
}

impl<'a> Node<'a> {
    fn genealogy(&self) -> Genealogy<'_> {
        let mut current_node = self;
        let mut genealogy = vec![current_node];
        while current_node.parent.is_some() {
            current_node = current_node.parent.unwrap();
            genealogy.push(current_node);
        }
        Genealogy(genealogy)
    }

    fn root(&'a self) -> &Self {
        self.genealogy().0.last().unwrap()
    }

    fn root_2(&'a self) -> &Self {
        self.genealogy().root()
    }
}

error[E0515]: cannot return value referencing temporary value
  --> src/lib.rs:31:9
   |
31 |         self.genealogy().root()
   |         ----------------^^^^^^^
   |         |
   |         returns a value referencing data owned by the current function
   |         temporary value created here

To test my lifetime knowledge, I tried to fix it with named lifetimes and it turned out I didn't really understand what is going on.
I don't understand why the two fn root make difference, root_2 is semantically the same as root except it calls an extra method?

    fn root(&'a self) -> &Self {
        self.genealogy().0.last().unwrap()
    }

    fn root_2(&'a self) -> &Self {
        self.genealogy().root()
    }

How do I understand the lifetime here and how do I fix it?

I'm a noob, so take my answer with a grain of salt.
I think the issue here is how the root() is defined:

impl Genealogy<'_> {
    fn root(&self) -> &Node<'_> {
        self.0.last().unwrap()
    }
}

My understanding is that this desugars to:

    fn root<'this>(&'this self) -> &'this Node<'this> {
        self.0.last().unwrap()
    }

This limits the borrow length of the resulting Node to the temporary Genealogy.
But you want it to be 'a:

    impl<'a> Genealogy<'a> {
        fn root(&self) -> &'a Node<'a> {
            self.0.last().unwrap()
        }
    }
3 Likes

I agree with @undefined.behavior: Genealogy::root "throws out" some lifetime information via supertype coercion. It is perhaps unintuitive due to all the anonymous lifetimes in that implementation.

Note also that Self preserves the implementing type exactly, including lifetimes. And elision rules apply even when input lifetimes are named. So e.g. root_2 is supposed to return a &'a Node<'a>.

2 Likes