[Solved] Why is a `'static` lifetime required here? Moving an owned variable into a thread

When I run the code below, I get a lifetime error error[E0310]: the parameter type `T` may not live long enough. My question now is why?

From my understanding, the go method on Hello is the owner of the loader. Therefore it should be able to move the loader into the thread. So why is it not able to do so?

use std::thread;
use std::marker::Send;

// Data provider trait
trait Hi {
    fn ha(&self);
}

// One data provider implementation
struct Species {
    genome: String,
}
impl Hi for Species {
    fn ha(&self) {
        println!("Hello");
    }
}

struct Hello {}
impl Hello {
    // Function to move the data provider into another thread.
    pub fn go<T>(&self, loader: T)
    where T: Hi + Send
    {
        thread::spawn(move || {
            loader.ha();
        });
    }
}

fn main() {
    let mut hi = Hello{};
    hi.go(Species { genome: String::new() });
}

(In my case the trait Hi is a data provider which I would like to replace for testing purposes.)

The generic T could be some type which is parameterized by a lifetime, if it contains a reference itself. Requiring T: 'static assures this is not so.

2 Likes

@cuviper is mostly right, but with one small wrinkle. 'static means that there are no references that live in local memory, which could be deallocated. If the references refer to global memory, then it is fine, because global memory will only be deallocated at the end of the program.

1 Like

To add to @cuviper, if you control the Hi (or the data provider) trait and otherwise want to ensure only 'static types implement it, you can add that constraint on the trait itself:

trait Hi: 'static { ... }

Then you don't need to repeat that elsewhere.

1 Like

@cuviper I did not even know that I could add a lifetime identifier to a where. With that lifetime specifier it compiles.

Thank you very much! I spend hours on this yesterday!!

@RustyYato Thank you for the clearification. This is not an issue here, since the thread will live through the whole execution of the application. However, if I would kill the thread, the instance would still survive, correct?

The issue isn't whether the thread will live for the whole application, rather if it will live longer than the spawning thread. If so, the any data the lives in the spawning thread will be destroyed, leaving behind dangling pointers. The 'static lifetime prevents this situation.

1 Like

The compiler is trying to prevent the following:

impl<'a> Hi for &'a i32 {
    fn ha(&self) { ... }
}
fn main() {
    let x = 5;
    hi.go(&x);
    // `x` is going to die after `main()` exits, as far as borrow checker is concerned.
}

By adding the 'static bound to go(), the above wouldn't compile (and if you add trait Hi: 'static, you can't even impl Hi for &'a i32).

1 Like

That does make sense.

@cuviper @RustyYato @vitalyd Thank you guys for your quick responses!