Struggling with lifetimes and borrow through structure

I am getting a lifetime error in the following code and I am unable to decipher the issue.

Please note that the Text trait is a simplified version from the graphql-parser library. Other types are similarly modeled after types in that library.

Playground

pub trait Text<'a>: 'a {
    type Value: 'a; 
}

impl<'a> Text<'a> for String {
    type Value = String;
}

pub struct Address<'a, T: Text<'a>> {
    pub street: T::Value,
}

pub struct Person<'person> {
    pub address: &'person Address<'person, String>,
}

pub fn process<'p>() {
    let address: Address<'p, String> = Address{street: "a street".to_string()};
    Person {
        address: &address
    };
}

The error is:

error[E0597]: `address` does not live long enough
  --> src/problem.rs:20:18
   |
17 | pub fn process<'p>() {
   |                -- lifetime `'p` defined here
18 |     let address: Address<'p, String> = Address{street: "a street".to_string()};
   |                  ------------------- type annotation requires that `address` is borrowed for `'p`
19 |     Person {
20 |         address: &address
   |                  ^^^^^^^^ borrowed value does not live long enough
21 |     };
22 | }
   | - `address` dropped here while still borrowed

If I change type of street in Address to String (and make attendant changes elsewhere), the error goes away. But in my real code, I can't do that (the equivalent types come from the graphql-parser library).

Please let me know any pointers to deal with this error.

From what I understand, it looks like function process Takes a lifetime parameter 'p, which means that anything that shares that lifetime must live at least that long. However, the address struct you create only lasts the lifetime of the function scope, while 'p is assumed to be external to the function scope. This means that the scope within the function is strictly shorter than 'p and thus the compiler is upset.

Ah, I see. Thanks! If I drop 'person from address, it compiles fine.

Here is the working version for future visitors:

pub trait Text<'a>: 'a {
    type Value: 'a; 
}

impl<'a> Text<'a> for String {
    type Value = String;
}

pub struct Address<'a, T: Text<'a>> {
    pub street: T::Value,
}

pub struct Person<'person> {
    pub address: &'person Address<'person, String>,
}

pub fn process() {
    let address: Address<String> = Address{street: "a street".to_string()};
    Person {
        address: &address
    };
}

Here is a variation of the original code snippet, which has a similar issue, but a solution isn't clear.

Playground

pub trait Text<'a>: 'a {
    type Value: 'a; 
}

impl<'a> Text<'a> for String {
    type Value = String;
}

pub struct Address<'a, T: Text<'a>> {
    pub street: T::Value,
}

pub struct Person<'person> {
    pub address: &'person Address<'person, String>,
    pub processor: &'person Processor<'person>
}

pub struct Processor<'a> {
    pub person: &'a Person<'a>
}

impl<'a> Processor<'a> {
    fn process(&'a self) {
        let address: Address<String> = Address{street: "a street".to_string()};
        Person {
            address: &address,
            processor: self,
        };
    }
}

I imagine that since address and processor both have the lifetime of 'person, and self in impl Processor outlives the local address, the lower of the two (that of address) will be the lifetime of the Person created and all should check out.

Please let me know any hints to resolve this.

In this code, I just noticed that we have a self reference, where a person has a processor and a processor has a person. This is very hard to do in rust and have lifetimes align. In fact there’s a rust tutorial that goes through it: Introduction - Learning Rust With Entirely Too Many Linked Lists

Of course, there’s a modeling error here I think. I think that either a person has a processor or a processor has a person, I’m not sure it should go both directions. Rust Playground

When I remove the processor reference the code compiles.

Thanks for your insight.

What's still unclear to me is when I make street a plain String, everything compiles.

The modeling error was more a result of my attempt to minimize the code to show here. But in any case, given the issue with referring self and aligning lifetime, I am going with a different structure to avoid the issue.

Ah, the String is heap allocated and without the & it is owned instead of borrowed. The way I think about it is that you need to know the lifetime of some owned data type in order to borrow a reference to it. If a struct owns another struct (in this case String) they will live (and be dropped) together. But if it’s a reference, you need to connect the lifetime of the pointer to the lifetime of whatever scope owns the value behind the reference.

This can get really tricky! So it’s OK to feel a little confused. And sometimes it’s just doesn’t fit. To work with these cases where you want to decouple the lifetimes, you can use a reference counter. This can make life a lot easier when lifetimes get tough. In multithreaded code a Atomic Reference Counter is very useful.

1 Like

Owned self-contained types don't have any lifetime at all. Lifetimes just don't apply to them. Lifetimes exist only for temporary borrows.

In generic code you don't have to declare all the lifetimes that are involved. Only lifetimes that you have to use in the generic code itself, or when multiple types depend on each other.

For example Vec<T> has no lifetime by itself. Lifetimes don't apply to Vec<String> at all, but you still can have Vec<&'a str> which will be limited by the lifetime.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.