"type annotations needed" with async, futures, boxes and pins

I suspect that there is a deeper problem with my code. I just can't get it fixed:

enum SomeTask<'a, F: Future> {
    Pinned(Pin<&'a mut F>),
    Boxed(Pin<Box<F>>),
    Result(F::Output),
}

impl<F: Future> From<F> for SomeTask<'_, F> {
    fn from(future: F) -> Self {
        Self::Boxed(Box::pin(future))
    }
}

impl<'a, F: Future> From<Pin<&'a mut F>> for SomeTask<'a, F> {
    fn from(pinned: Pin<&'a mut F>) -> Self {
        Self::Pinned(pinned)
    }
}

impl<F: Future> SomeTask<'_, F> {
    fn new(future: impl Into<Self>) -> Self {
        future.into()
    }
}

async fn test_some_task() {
    let f1 = async { 123 };
    let t1 = SomeTask::new(f1);

    let f2 = async { 456 };
    let p2 = pin!(f2);
    let t2 = SomeTask::new(p2);
}

The last line fails with "type annotations needed". But what concrete type should I add? I don't know a concrete type of that future, do I?

No, you don’t, know the concrete type indeed, so you should probably avoid building an API that’s so generic that those ambiguities appear in the first place.


In case you’re wondering, is it possible to use at all? Yeah, use a generic wrapper that can name the generic type, like

fn new_some_task_pinned<'a, F: Future>(f: Pin<&'a mut F>) -> SomeTask<'a, F> {
    SomeTask::new(f) // not ambigous because of return type above
}

async fn test_some_task() {
    let f1 = async { 123 };
    let t1 = SomeTask::new(f1);

    let f2 = async { 456 };
    let p2 = pin!(f2);
    let t2 = new_some_task_pinned(p2);
}

does this kill all intended ergonomics of your API completely? I’d guess: also, yes – you now could’ve written let t2 = SomeTask::Pinned(p2); to begin with.

Hmm. But why does the compiler succeed with t1 but not with t2? I’d really appreciate understanding the root cause of this issue.

The first case, you have a type – let’s call it – MyAsyncBlock and f1: MyAsyncBlock and impls

impl<'a, G> From<G> for SomeTask<'a, G>
impl<'a, H> From<Pin<&'a mut H>> for SomeTask<'a, H>

Then MyAsyncBlock fits the input type of only one of these impls:

  • the first one, with G = MyAsyncBlock so that G fits the type of f1.
    • under this substitution, the impl looks like
      impl<'a> From<MyAsyncBlock> for SomeTask<'a, MyAsyncBlock>

The second case, you have a type MyAsyncBlock2 but construct p2: Pin<&'l mut MyAsyncBlock2>[1] from it. Now this type of p2 fits the input types of both of the impls, respectively:

  • the first one, with G = Pin<&'l mut MyAsyncBlock2>, so that G fits the type of p2, and
    • under this substitution, the impl looks like
      impl From<Pin<&'l mut MyAsyncBlock2>> for SomeTask<'a, Pin<&'l mut MyAsyncBlock2>>
  • the second one, with H = MyAsyncBlock2,[2] so that Pin<&'l mut H> fits the type of p2.
    • under this substitution, the impl looks like
      impl From<Pin<&'l mut MyAsyncBlock2>> for SomeTask<'l, MyAsyncBlock2>

  1. for some lifetime 'l ↩︎

  2. and 'a = 'l ↩︎

1 Like

Ah, of course, Pin<SomeFuture> implements Future itself. I hadn’t thought of that...

Thank you so much for the help!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.