2

Method 1 (WORKS):

pub fn spawn_good<F, R>(future: F) -> JoinHandle<R>
where
    F: Future<Output = R> + 'static + Send,
    R: Send + 'static,
{
    let (sender, recvr) = oneshot::channel();
    let future = async move {
        sender.send(future.await);
    };

    let task = Arc::new(Task {
        state: AtomicUsize::new(0),
        future: Mutex::new(Box::pin(future)),
    });

    Box::pin(async { recvr.await.unwrap() })
}

Method 2 (DOESN'T WORK):

pub fn spawn_bad<F, R>(future: F) -> JoinHandle<R>
where
    F: Future<Output = R> + 'static + Send,
    R: Send + 'static,
{
    let (sender, recvr) = oneshot::channel();
    let future = async move {
        sender.send(future.await);
    };

    let future = Mutex::new(Box::pin(future));

    let task = Arc::new(Task {
        state: AtomicUsize::new(0),
        future,
    });

    Box::pin(async { recvr.await.unwrap() })
}
expected struct `std::sync::Mutex<Pin<Box<(dyn futures::Future<Output = ()> + std::marker::Send + 'static)>>>`
   found struct `std::sync::Mutex<Pin<Box<impl futures::Future<Output = [async output]>>>>`

Task definition:

struct Task {
    state: AtomicUsize,
    future: Mutex<Pin<Box<dyn Future<Output = ()> + Send>>>
}

JoinHandle definiton:

type JoinHandle<T> = Pin<Box<dyn Future<Output = T> + Send>>;

I don't see why the second method won't work. The only difference is an additional allocation. Is there some kind of nuance with Mutex I am missing?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • 1
    Can you link to a Playground that shows the `[async output]` error? I tried to reproduce it but am getting a [slightly different error message](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=febba6905e1bd68df733982307ce6e37). – John Kugelman Jun 06 '22 at 18:23
  • Can't get a link to a playground, perhaps i'll change the question to be about opaque type message because that is common on both playground and my IDE – infinitesimallySmart Jun 06 '22 at 18:32

1 Answers1

2

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!").

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77