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.
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.
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.
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.
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.