Lifetimes and Generics


#1

I’m writing a program with multiple threads and wanted to normalize starting the various tasks, so I wrote this:

use std::marker::Send;
use std::string::String;
use std::thread::{Builder, JoinHandle};

pub trait Task : Send {
    fn run(&mut self) -> u32; 
}

pub fn start_task<T: Task>(name: String, task: T) -> JoinHandle<u32> {
    let builder   = Builder::new();
    let with_name = builder.name(name);

    match with_name.spawn(move || { task.run() }) {
        Ok(handle) => handle,
        Err(err) => {
            panic!("Thread failed to start: {}", err);
        }
    }
}

However Rust is saying:

59:50 help: run `rustc --explain E0310` to see a detailed explanation
59:50 help: consider adding an explicit lifetime bound `T: 'static`...
59:50 note: ...so that captured variable `task` does not outlive the enclosing closure
match with_name.spawn(move || { task.run() })

My understanding is the parameter “task: T” would be passing the ownership of object into the method, not a reference, therefor the lifetime of “task” should match the function, and the “move || {}” moves the ownership of task parameter into the spawned thread. But rust’s error seems to be saying no, so obviously I’m missing something…

If I remove the generics and create a structure “Task” with a method named run() the compiler seems to be happy but then doesn’t really achieve what I’m trying to do.

Any ideas?


#2

Oh I probably should mention I tried adding 'static to various places, but reading the lifetime documentation, again, I really don’t know where to add it, and every place I tried the compiler would complain.


#3
use std::marker::Send;
use std::string::String;
use std::thread::{Builder, JoinHandle};

pub trait Task : Send {
    fn run(&mut self) -> u32; 
}

pub fn start_task<T: Task+'static>(name: String, mut task: T) -> JoinHandle<u32> {
    let builder   = Builder::new();
    let with_name = builder.name(name);

    match with_name.spawn(move || { task.run() }) {
        Ok(handle) => handle,
        Err(err) => {
            panic!("Thread failed to start: {}", err);
        }
    }
}
fn main() {}

Any specific suggestions for improving the error message or documentation would be welcome.


#4

To add some explanation to @eefriedman’s solution. The trick here is that the compiler doesn’t know what T is. It knows that it is Send and that it is Task, but other than, it’s mostly up for grabs. Even though T is passed by ownership into your function, it can still contain borrowed references! For example:

struct MyType<'a> {
    my_val: &'a u32,
}
let n = 2u32;
let v = MyType { my_val: &n };
start_task("wat", v);

Without the 'static bound (and assuming MyType impls Task), this would be entirely legal in any function that takes T: Task. But the compiler sees that you’re trying to pass that MyType value into spawn, which has a 'static bound defined on it. Since your function doesn’t have a 'static bound, the compiler produces an error.

Note that MyType is not 'static because it contains a borrowed reference. The 'static bound basically says, “You Must Give Me Data That Is Not Borrowed!” In other words, it must be data that has no restrictions on its lifetime.


#5

Okay that makes sense, thanks!

For anyone having the same issue just for completeness this is the code I ended up with:

use std::marker::Send;
use std::string::String;
use std::thread::{Builder, JoinHandle};

pub trait Task
{
    fn run(&mut self) -> u32; 
}

pub fn start_task<T: Task + Send + 'static>(name: String, task: T) -> JoinHandle<u32> {
    let builder   = Builder::new();
    let with_name = builder.name(name);
    let mut my_task = task;

    match with_name.spawn(move || { my_task.run() }) {
        Ok(handle) => handle,
        Err(err) => {
            panic!("Thread failed to start: {}", err);
        }
    }
}

I had to add a “let mut my_task = task” line otherwise there was a compiler error about borrowing an immutable outer variable.


#6

You could write mut task: T in the function signature instead.


#7

Okay that just hurts me.

Any idea why I couldn’t use task: mut T for the parameter. I mean if I wanted to pass a reference I would have used task: &T and if I wanted to pass a mutable reference I would have used task: &mut T.

And I guess if I passed mut task: &mut T I would be passing a mutable reference to a mutable object. Urk, I really hope I never have to use that, it just seems like a way to confuse future me.


#8

It’s not syntactically valid. Using mut task: T in your function parameter list is just a shortcut for writing let mut task = task;. In this case, mut is a property of the named binding task. It means that task can change its value. If you wrote let task = ...;, then the only way task can change is if it’s shadowed by another binding.

The above is completely orthogonal to whether you want to pass a reference or not. :slight_smile:


#9

In this case the mutability is not a property of the type (T already gives you full ownership), but of the binding.


#10

Okay I believe I understand now, thanks.