How to solve returning this value requires that `'life1` must outlive `'static`

Not exactly. Your second example only works because person.name points to the variable name which still exists when person is dropped. So in essence val is just &name.

The concept that you need to understand is that lifetimes are an intrinsic property of things that the compiler needs to keep track of, such as references and values. We just annotate them to help the compiler to figure them out when needed.

Not exactly. Your second example only works because person.name points to the variable name which still exists when person is dropped. So in essence val is just &name .

Yes, I understand that val is just &name.
But I thought if 'a corresponds to the lifetime of person, person.name becomes invalid when person is dropped because person.name is annotated with 'a in the struct definition.
I think I am missing something basic here.

(Update)
My assumption seems wrong.
The annotation is just saying that the name field must have a lifetime that is at least as long as 'a.

@moy2010

Sorry for asking repeatedly but when I remove 'a from &'a self, it compiles.
Does this mean 'a now corresponds to the lifetime of name?

What I don't understand is when you say caller provides the lifetime, how it affects or interacts with (?) 'a of Person<'a>?

Correct. When you removed the annotation from the method's reference to self, the compiler assigned an anonymous, shorter lifetime to it. Something like this:

impl<'b, 'a: 'b> Person<'a> {
    fn get_name(&'b self) -> &'a str {
        return self.name;
    }
}

In that example, you are borrowing self for a lifetime 'b, and the compiler understands that 'b must be shorter than 'a.

As you said yourself, in your previous example Person took the lifetime of the variable name. That's what people mean when they say "the caller provides the lifetime".

For example, if you move name inside the block, it won't work again:

fn main() {
    let val: &str;
    {
        let name = String::from("john");
        let person = Person {
            name: &name,
        };
        val = person.get_name();
    }
    println!("{}", val);
}

When a struct will only live as long as a reference being passed to it, the amount of things you can do with it become pretty limited. For this and many other reasons, declaring structs with lifetimes is considered an anti-pattern. Instead, it's preferred if the structs own all of their data:

struct Person {
    name: String
}

impl Person {
    fn get_name(&self) -> &str {
        return &self.name;
    }
}

fn main() {
    let val: &str;
    let person = Person {
        name: String::from("john")
    };
    val = person.get_name();
    println!("{}", val);
}

Your code becomes much cleaner and readable.

1 Like

My understanding on lifetime is getting much better... thanks.

That's what people mean when they say "the caller provides the lifetime".

Is it correct to say that "caller" includes not only method or function caller but also caller of struct constructor?

For this and many other reasons, declaring structs with lifetimes is considered an anti-pattern.

I see. Thanks.

Yes. Anything that can be parameterized with a lifetime.

1 Like

Thank you so much guys.
I cannot say I fully understood everything but at least I understood what is causing the compilation error in the code of my original post and possible fix as well as the concept of caller providing lifetime.

(By the way I don't know which post to mark as a solution.)

That's not correct. Person<'a> is covariant in 'a and so is &'a Person<'a>.[1] You can shorten both lifetimes when you make the call.[2]

The problem in that example is that you can't have a borrow of something that lasts after said thing goes out of scope.

    let val: &str;
    let name = String::from("john");
    {
        let person = Person { // Person<'p>
            name: &name, // `name` is borrowed for `p`
        };
        // Create `&'v Person<'v>` for method call, get back `&'v str`
        // `'p: 'v` and `person` is borrowed for `'v`
        val = person.get_name();
    } // `person` goes out of scope -- incompatible with `'v` being alive

    // Use of `'v` -- requires `'v` being alive
    println!("{}", val); 
    // End of block drops `name`, incompatible with `'p` being alive

In contrast here

    let val: &str;
    let name = String::from("john");
    {
        let person = Person { // Person<'p>
            name: &name,  // `name` is borrowed for `p`
        };
        // for simplicity let's say `val: &'p str` and this is a copy
        val = person.name;
    } // `person` goes out of scope

    // Under this interpretation this is just a use of `'p`
    println!("{}", val); 
    // End of block drops `name`, incompatible with `'p` being alive

A simple way to think of this is that you just copied the reference out of person.[3] It's okay if 'p is longer than the inner block even though Person goes out of scope, just like it's okay to have a &'static in a local variable.

The actual borrow analysis is more complicated, but hopefully you understand how the compiler can prove everything is ok for this example.

I believe this is how it actually works, but probably it's not worth thinking so hard about for the example since "you just copied the reference out" also justifies the compilation succeeding.

EDIT: Ehh, sorry, what I had here wasn't correct and I don't feel like fixing it (as it basically does end up effectively being "you made a copy" I believe now) so I'm just going to deleted it.


Here's the signature now:

// fn get_name(&self) -> &'a str
fn get_name<'s>(self: &'s Person<'a>) -> &'a str
where
    'a: 's // implicit bound from the `&'s ... 'a` nesting

So now when you call this method, you can borrow self for just the call expression ('s) and return some longer lifetime ('a).[4] Compared to the first example above,

        //          vv DIFFERENT new lifetime (and `'v: 's`)
        // Create `&'s Person<'v>` for method call, get back `&'v str`
        // `'p: 'v` and `person` is borrowed for `'s` << DIFFERENT
        val = person.get_name();

's isn't used anywhere else so it can expire immediately, which means person isn't borrowed when it goes out of scope. The reborrow of the field is hidden away in the method (beyond an API contract that the compiler assumes just works), so the more complicated "shallow access" logic hidden under the fold above doesn't even need apply. [5] It's sort of like you codified the "take a copy" interpretation into the method signature, if you will.

If you actually need a borrowing struct that you hand out borrows from, this is almost always what you want/mean: You can borrow me very briefly (&'s self) and I can give you part of the borrow I'm holding on to (something with 'a). It's how iterators that hand out references work, for example.


I didn't see much else to comment on, but let me know if you have any follow-up questions.


  1. The statement would be true for &'a mut Person<'a> though. ↩︎

  2. If you're not convinced: removed the println! so it compiles and then add a let _person = person after the call; if person was borrowed forever you wouldn't be able to move it. ↩︎

  3. or moved it out if it was a &mut. ↩︎

  4. Due to covariance again, this could be shorter than the lifetime of person, but that doesn't happen to matter in this example really. ↩︎

  5. Edit: I messed that part up and removed it and don't feel like it's worth spelling out ↩︎

4 Likes

@quinedot
Thank you for your explanation.

Here's my understanding. Is it correct?
When the compiler sees the instantiation of Person it binds 'a to the lifetime of name ('p in your example). And when
it sees person.get_name() it binds a shorter lifetime to 'a which is the lifetime of person.
As you said, it can shorten the lifetime of Person<'a> and &'a Person<'a> because they are covariant in 'a.

Yes.[1]

It binds a new lifetime (that I called 'v) and calls <&'v Person<'v>>::get_name(&person). I don't know that I would call that the lifetime of person. The use of val does force the variable person to stay borrowed until the println -- or if that's not possible, cause a borrow error.


  1. It might technically use one lifetime for &name and one shorter one for Person<'_>, but if so it's irrelevant here. ↩︎

1 Like

I thought this was interesting/useful enough to write up in one place:

Feedback welcome.

2 Likes

It has passed some time but I am trying to make callback function op receive a repository instance that holds a reference to transaction object tx.
I expect this allows the callee of op to execute DB operations with transaction enabled.

Here's a part of my code. (The entire code is available on GitHub)
Op<'a> now takes Repository (which contains PersonRepository) as its parameter.

pub type Op<'a> = Box<dyn 'a + Send + FnOnce(Repositories) -> BoxFuture<'a, Result<()>>>;

#[async_trait]
pub trait Transaction {
    async fn execute(&self, op: Op<'static>) -> Result<()>;
}

pub struct SeaOrmPersonRepository<'a> {
    pub db: Arc<SeaOrmConnection<'a>>,
}

#[async_trait]
impl<'a> PersonRepository for SeaOrmPersonRepository<'a> {
    async fn fetch_one(&self, id: i32) -> Result<Option<domain::person::Person>> {
      // omitted
    }
    async fn save(&self, person: domain::person::Person) -> Result<()> {
      // omitted
    }
}

pub struct Repositories {
    pub person_repository: Arc<dyn PersonRepository + Send + Sync>,
}

pub struct SeaOrmTransaction {
    pub db: Arc<DatabaseConnection>,
}

#[async_trait]
impl<'a> Transaction for SeaOrmTransaction {
    async fn execute(&self, op: Op<'static>) -> Result<()> {
        self.db.transaction::<_, (), DbErr>(|tx| {
            let c = async move {
                let db = Arc::new(SeaOrmConnection::Transaction(Arc::new(tx)));
                let person_repository = Arc::new(SeaOrmPersonRepository {
                    db: db.clone(),
                });
                let res = op(Repositories {
                    person_repository,
                }).await;
                if let Err(_) = res {
                    return Err(DbErr::Custom(String::from("error")));
                }
                Ok(())
            };
            Box::pin(c)
        })
        .await?;
        Ok(())
    }
}

But the code does not compile, with the following error.

  --> src/infra/repository.rs:50:21
   |
43 |         self.db.transaction::<_, (), DbErr>(|tx| {
   |                                              -- has type `&'1 sea_orm::DatabaseTransaction`
...
50 |                     person_repository,
   |                     ^^^^^^^^^^^^^^^^^ cast requires that `'1` must outlive `'static`

My understanding of the error message is, Op<'static> requires any references held by person_repository (directly or indirectly) to live 'static.
tx is a reference held by person_repository indirectly. But the callee does not know how long tx will live, so it ends up with compilation error.

My ultimate goal is to make transaction logic library-agnostic.
Is there anything I am missing? Is there any workaround to achieve my goal?