Reputation: 327
I have a struct Task
which is defined as below:
pub struct Task<F>
where
F: Future<Output = ()>,
{
pub name: &'static str,
pub should_run_late: bool,
pub time: Time,
runner: fn() -> F,
}
impl<F> Task<F>
where
F: Future<Output = ()>,
{
pub async fn run(self) {
(self.runner)().await;
}
pub fn set_Runner(&mut self, func: fn() -> F)
{
self.runner = func;
}
}
and call it this way
tokio::spawn(async move {
task.run().await;
});
I'm putting Tasks in a vector in some struct like this:
some struct {
pub tasks: HashMap<String, HashMap<String, Vec<Task<dyn Future<Output = ()>>>>>
}
The problem is in the above which says it doesn't implement Sized
trait and when then wrapped it with Box:
HashMap<String, HashMap<String, Vec<Task<Box<dyn Future<Output = ()>>>>>>
It says to use Box::pin. It really makes everything confusing. I'm fairly new in rust, and what ever I try I can't make it.
Upvotes: 1
Views: 786
Reputation: 43842
Changing the type of the F
parameter in the Vec<Task<F>>
won't help, since as written, F
must match the actual return type of the runner
function. You need to handle the function “dynamically” too, not just the future. Once you do, the code can also be simplified. Here's a new definition of Task
:
pub struct Task {
pub name: &'static str,
pub should_run_late: bool,
pub time: Time,
runner: Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>,
}
Notice that the stored function returns Pin<Box<dyn Future>>
, so we need to wrap the function when it's stored by set_runner
. (This is going to match the Box::pin()
that you noticed being asked for.) Actually running the task is the same:
impl Task {
pub async fn run(self) {
(self.runner)().await;
}
pub fn set_runner<Func, Fut>(&mut self, func: Func)
where
Func: Send + 'static + FnOnce() -> Fut,
Fut: Send + 'static + Future<Output = ()>,
{
self.runner = Box::new(move || Box::pin(func()));
}
}
I generalized the func
parameter so it will accept closures and not just function pointers — since we're boxing the function anyway, this is free.
Compilable sample on Rust Playground
Now, this code can be a bit messy to read. There are a couple of libraries providing trait aliases which can help you out by removing the boilerplate. The futures
library contains futures::future::BoxFuture
which tidies up storing a boxed future:
use futures::future::BoxFuture;
pub struct Task {
...
runner: Box<dyn FnOnce() -> BoxFuture<'static, ()> + Send>,
}
For the input to set_runner
, the two lines of trait bounds are just saying "Func
is an async function which takes no arguments and returns ()
". We can simplify that (somewhat) using async_fn_traits
:
use async_fn_traits::AsyncFnOnce0;
impl Task {
...
pub fn set_runner<F>(&mut self, func: F)
where
F: AsyncFnOnce0<Output = ()> + Send + 'static,
F::OutputFuture: Send + 'static,
{
self.runner = Box::new(move || Box::pin(func()));
}
}
In situations that don't involve storing the future this would be even more of an improvement (because we would not need to write out the Send + 'static
bounds), but at least here we avoid needing the separate Fut
type parameter which can't actually vary independently.
Using these aliases is entirely optional — it's up to you which version of the code you think is clearer. For what it's worth, futures
is quite commonly used for other future-related utilities, and async_fn_traits
is considerably more obscure.
Upvotes: 4