Every {async block}
type implements the Future
trait. The problem you are having is that you are failing to coerce it to dyn Future
. The subtlety here is that you have to provide an opportunity to do so at the right moment. Here's a program which fails for the same reason that involves no async blocks or even Pin
:
let x = || Box::new("hello");
let y: Box<dyn Fn() -> Box<dyn std::fmt::Display>> = Box::new(x);
error[E0271]: expected `{closure@main.rs:2:13}` to be a closure that returns `Box<dyn Display>`, but it returns `Box<&str>`
--> src/main.rs:3:58
|
3 | let y: Box<dyn Fn() -> Box<dyn std::fmt::Display>> = Box::new(x);
| ^^^^^^^^^^^ expected `Box<dyn Display>`, found `Box<&str>`
|
= note: expected struct `Box<dyn std::fmt::Display>`
found struct `Box<&str>`
The trick is, a type can be coerced to a dyn
type only if it is wrapped in exactly one pointer type (such as Box<T>
, &T
, or Pin<Box<T>>
) at the moment when the compiler notices that coercion must occur to avoid a type error. The coercion will not work if that type to be coerced is wrapped in anything else — including a closure. The problem in my small example is that the return type of the closure has already been settled as Box<&'static str>
by the time we try to coerce the closure itself. This could be fixed by adding a return type to the closure, to ensure the code inside the closure includes the necessary coercion:
let x = || -> Box<dyn std::fmt::Display> { Box::new("hello") };
But, a usually-more-elegant fix is to ensure the entire closure is assigned (or returned) to a location with an explicit type:
let y: Box<dyn Fn() -> Box<dyn std::fmt::Display>> =
Box::new(|| Box::new("hello"));
This works because the Rust compiler has a special rule to help with the thorny problem of defining the signature of a closure: if the closure expression is written inside an expression that gives it a signature (like let x: SomeType = Box::new(|| ...)
or some_function_with_a_fn_bound(|| ...)
) then that signature will be used instead of inferring from the type of the values inside the closure’s code. So, as a general habit, you should avoid writing let x = || ...
whenever possible because it is likely to fail. Whenever possible, keep the ||
nested inside the part of the code that gives it a type. In your actual case, that's:
let action = Action {
action: Box::new(|x| Box::pin(async { x })),
};
Note that passing a closure to Box::new
doesn't count as giving it a type, because Box::new
is generic over arbitrary T
, not T: Fn(...)
, so it doesn't constrain the closure signature; instead, Action
is doing that. For boxed closures, the whole Box::new(|| ...)
or Box::pin(|| ...)
is the expression that you need to give a type to.
I've also removed the IIFE you had in there; it would interfere with getting the right outcomes too, because IIFEs don't have anything to set their closure signature so they can be troublesome too.
Finally, I think I should prove that this still works with a borrow in the future, since that was your original problem:
use std::future::Future;
use std::pin::Pin;
#[derive(Clone)]
struct Value;
type Return<'a> = Pin<Box<dyn Future<Output = Value> + 'a>>;
struct Action {
action: Box<dyn for<'a> FnMut(&'a Value) -> Return<'a>>,
}
fn main() {
let action = Action {
action: Box::new(|val| Box::pin(async { val.clone() })),
};
}
(You will probably want to make both the closure and the async block move
, but that's orthogonal to everything else we discussed. It matters if they are going to capture anything from outside.)