Arc parameters moved into closure fails lifetime check

I'm quite new to Rust, and I have a question regarding lifetimes I encountered when trying to make a program more clean by moving some code to a function. I have a somewhat minimal example below that reproduces the problem

Playground link

use std::sync::Arc;
use std::thread;

struct SomeStruct<'a> {
    s: &'a str,
}

trait SomeTrait<'a> {
    fn new(s: &'a str) -> Self;
}

impl<'a> SomeTrait<'a> for SomeStruct<'a> {
    fn new(s: &'a str) -> Self {
        Self {
            s,
        }
    }
}

fn f<'a, T: SomeTrait<'a>>(s_arc: Arc<String>){
    thread::spawn(move || {
        T::new(&s_arc.as_str());
    }).join();
}

fn main() {
    let k = String::from("Helooo");
    let karc = Arc::new(k);
    f::<SomeStruct>(Arc::clone(&karc));
}

which results in the following error:

error[E0597]: `s_arc` does not live long enough
  --> src/main.rs:22:17
   |
20 | fn f<'a, T: SomeTrait<'a>>(s_arc: Arc<String>){
   |      -- lifetime `'a` defined here
21 |     thread::spawn(move || {
22 |         T::new(&s_arc.as_str());
   |         --------^^^^^^^^^^^^^^-
   |         |       |
   |         |       borrowed value does not live long enough
   |         argument requires that `s_arc` is borrowed for `'a`
23 |     }).join();
   |     - `s_arc` dropped here while still borrowed

Note that the string here is just a stand-in for more complex structs, as is everything else.

Ok, so s_arc is dropped at the end of f. But should it not be moved into the closure at row 21? From the error message I conclude that the lifetime parameter 'a is involved here somewhere.

The thing is, the original code looks something like this (but longer and more noodle-like)

Playground link

// ... (Same as above)
fn main() {
    let k = String::from("Helooo");
    let karc = Arc::new(k);

    let handle = {
        let c_karc = Arc::clone(&karc);
        thread::spawn(move || {
            SomeStruct::new(&c_karc);
        })
    };

    handle.join().unwrap();
}

For me this looks like basically the same code, but obviously that's not correct.

I assume a simple solution would be to just make SomeStruct take ownership of s through something like String, but is there another solution? Is this perhaps the best option, to avoid lifetimes altogether? The real SomeStruct mostly contains Arc<T> fields, though typed as & references.

Thanks in advance for any help!

Edit: Fixed formatting

When you have a lifetime parameter on a function (like f), the caller chooses the lifetime. They have to meet any bounds you set (you have none), but other than that it can be any lifetime the caller can name. Maybe they'll call it with 'static.

The caller can't name lifetimes shorter than the function body, so the one thing you (the function writer) know for sure is that the lifetime is longer than your function body.

So, that's the immediate cause of the error. The Arc was moved into the closure, but you're trying to borrow it for some lifetime the caller chooses, which has nothing to do with how long the closure lasts.

Your main version works because there's no lower limit (or "work with all lifetimes" generic requirement) on the inline inferred lifetime there.


How do you indicate that you need the caller to support a lifetime shorter than the function body then? Normally you require them to support any lifetime, even those too short for them to name, by using a higher-ranked trait bound (HRTB):

fn f<T: for<'a> SomeTrait<'a>>(s_arc: Arc<String>) {

Then the closure could just briefly borrow the Arc it owns. However, this alone doesn't work for your playground, because SomeStruct<'a> only implements SomeTrait<'a>.

What you need in this case is for your trait to be higher ranked in some sense. Generic associated types (GATs) will stabilize later this year, and with them you could write it like so:

trait SomeTrait {
    type BorrowedSelf<'a>;
    fn new(s: &str) -> Self::BorrowedSelf<'_>;
}

impl SomeTrait for SomeStruct<'static> {
    type BorrowedSelf<'a> = SomeStruct<'a>;
    fn new(s: &str) -> Self::BorrowedSelf<'_> {
        SomeStruct {
            s,
        }
    }
}

And now a SomeTrait bound is enough to indicate that you can call new with any lifetime, similar to a HRTB. Here, I've used SomeStruct<'static> to stand in for "construct SomeStruct<'any_lifetime>, but in other cases (where you have blanket implementations) you may need to use some other standin type.

struct SomeStructConstructor;
impl SomeTrait for SomeStructConstructor {
    type BorrowedSelf<'a> = SomeStruct<'a>;
    // ...
}

With a bit more boilerplate, you can emulate lifetime GATs on stable too.

1 Like

What a good answer, thank you!

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.