This is similar to this question. When you assign future
to the newly created Task
, and since Task::future
is of type Mutex<Pin<Box<dyn Future<Output = ()> + Send>>>
, this becomes a constraint for type inference: future
should have type Mutex<Pin<Box<dyn Future<Output = ()> + Send>>>
.
Now, future
actually has the type Mutex<Pin<Box<[some future generated by async block]>>>
(let's name [some future generated by async block]
Fut
from now), not Mutex<Pin<Box<dyn Future<Output = ()> + Send>>>
. Pin<Box<Fut>>
can coerce to Pin<Box<dyn Future<Output = ()> + Send>>
(and also Box<Fut>
can coerce to Box<dyn Future<Output = ()> + Send>
), but will do so only if the compiler deems this is necessary. On the other hand, Mutex<Pin<Box<Fut>>>
cannot coerce to Mutex<Pin<Box<dyn Future<Output = ()> + Send>>>
, because coercion isn't recursive (T
can coerce to U
doesn't mean A<T>
can coerce to A<U>
).
The important point is, the compiler only checks whether a coercion should be performed at special coercion sites. Instantiation of a struct is a coercion site for each field expression, and so are method calls, but variable declarations let
statement without an explicit type are not coercion sites. We can follow the compiler inference:
For the first, working case:
The expression provided for Task::future
should have type Mutex<Pin<Box<dyn Future<Output = ()> + Send>>>
, i.e. this is its expected type (or, the compiler has an Expectation
for it to be of that type).
Since Mutex::new()
has the signature fn<T>(T) -> Mutex<T>
, and we know it should return Mutex<Pin<Box<dyn Future<Output = ()> + Send>>>
, we can conclude that the value of T
is Pin<Box<dyn Future<Output = ()> + Send>>
. So, its parameter also should have that type, or this is its expected type.
Ditto for Box::pin()
, with a signature of fn<T>(T) -> Pin<Box<T>>
. Now T
is dyn Future<Output = ()> + Send
. We do not provide a value of that type: we provide a value of type Fut
. But don't give up! Let's trace back and try to find a good coercion. Fut
cannot be coerced to dyn Future<Output = ()> + Send
directly, but if we go back one step, we see Pin<Box<Fut>>
that can be coerced to Pin<Box<dyn Future<Output = ()> + Send>>
. This is inside a method call (Mutex::new()
) so it can be coerced here. Problem solved.
Now let's follow the second case:
First, we infer the type for the future
variable. We have no expectation for it, because no type is specified. We have no expectation for T
in Mutex::new()
or the one in Box::pin()
either. So we can conclude from the value that T
for Box::pin()
is Fut
, and then T
for Mutex::new()
is Pin<Box<Fut>>
. Thus future
has type Mutex<Pin<Box<Fut>>>
.
Now, when trying to type-check the expression for the field future
, we notice it has type Mutex<Pin<Box<Fut>>>
while its expected type is Mutex<Pin<Box<dyn Future<Output = ()> + Send>>>
. We cannot coerce the first to the second, but neither we can propagate the coercion, because let
is not a coercion site. So we declare an error.
The fix is simple: you should insert a coercion site. You can either specify the type for the let future
, and make the compiler coerce it there, or specify as _
after Box::pin()
(Mutex::new(Box::pin(future) as _)
, of course specifying the type will also work), forcing the compiler to put a coercion site there (this is quite confusing, because method calls are already coercion sites, but by default the compiler doesn't coerce there because it infers T
by the parameter. Putting as _
there is like telling the compiler "Stop! I want to disconnect the return type from the argument's type, it is not necessarily Pin<Box<[parameter type]>>
, it only should be coercible (castable, to be precise) to it!").