Impl Future with/without Send bound gives different compilation results

#![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)]
use futures::Future;

pub trait Iter: Send {
    type Item;
    type NextFuture<'a>: Future<Output = Option<Self::Item>> + Send
    where
        Self: 'a;

    fn next(&mut self) -> Self::NextFuture<'_>;
}

pub struct IterWrapper<I> {
    inner: I,
}

impl<I> Iter for IterWrapper<I>
where
    I: Iter<Item = (u8, u8)>,
{
    type Item = (u8, u8);

    type NextFuture<'a> = impl Future<Output = Option<Self::Item>> + Send where Self: 'a;

    fn next(&mut self) -> Self::NextFuture<'_> {
        async move {
            let pair = self.inner.next().await;

            pair
        }
    }
}

This piece of code would produces:

   Compiling playground v0.0.1 (/playground)
error[E0311]: the parameter type `I` may not live long enough
  --> src/lib.rs:27:9
   |
18 |   impl<I> Iter for IterWrapper<I>
   |        - help: consider adding an explicit lifetime bound...: `I: 'a`
...
27 | /         async move {
28 | |             let pair = self.inner.next().await;
29 | |
30 | |             pair
31 | |         }
   | |_________^ ...so that the type `I` will meet its required lifetime bounds

error: could not compile `playground` due to previous error

I thought Self: 'a already implies that I: 'a.

If I remove the Send bound on NextFuture, then the compiler is happy. But Send is necessary as Future can be passed among threads to be executed.

How should I pass the compiler with Send bound?

Why would you await in the iter loop? There is a cost to await - it isn't just syntactic: you get sent to the back of the run queue. Seeing them so liberally in code worries me. Futures of futures seem inconsistent to me - the compiler should flatten them it seems - possibly integrated with Result better.

Why would you await in the iter loop? There is a cost to await - it isn't just syntactic: you get sent to the back of the run queue. Seeing them so liberally in code worries me. Futures of futures seem inconsistent to me - the compiler should flatten them it seems - possibly integrated with Result better.

This is not true: await does not surrender control to the executor, and it does not put you at the end of the run queue. All await does is transfer direct control to the future being awaited on. It does not return control to the executor unless that future, or another future awaited on, makes that decision (by waiting on IO, or something else).

If you have one function that awaits another, the second function starts execution immediately, and this is guaranteed by rust's async model. There's no surrender of control to the executor unless and until the future you've awaited surrenders control.

Put another way, these two blocks of code will give you different types, but other than that, the futures they produce behave identically:

future

and

async move { future.await }

Unrelated, I'm still trying to figure out the OP's problem. May edit this post with a solution if I can.

3 Likes

I'm really confused by your comment. I don't see how these call all be true at once:

  • await does not surrender control to the executor
  • another future awaited on, makes that decision (by waiting on IO, or something else).
  • All await does is transfer direct control to the future being awaited on

So it does transfer control back if the futures being polled still wants something - even if the current line of execution can still make progress? And wath do you mean by "direct".

For me, it is everybody's simplifying away crucial details that are making this very opaque and difficult to understand. I've been told by varying people and read very different things.

For example, you say "all it does" but you leave out one of the largest pieces, creating a yield point. When you "behave identically" that isn't very useful for a detailed understanding of what is happening. That doesn't seem to be the cast at all for me or others. I've read a couple reports of tasks being buried behind a long run queue from await. I routinely see 5 ms delays that I think can be attributed to await stalls, but I can't be sure.

I know there are a couple open issues for missed optimizations (a couple being inlining related) but I'm not sure how much those will affect anything. I don't have the time or rust ability to wite a latency test (all I've seen are throughput related), but hopefully someone can.

There is an example of how async/await is compiled here under the "The Async/Await Pattern" heading.

What @daboross is referring to will, in that example, translate to the fact that it is possible for the ExampleStateMachine to transition from the Start state to the End state in a single call to ExampleStateMachine::poll if both state.foo_txt_future.poll and state.bar_txt_future.poll return Poll::Ready immediately. On the other hand, if one of the poll calls returns Poll::Pending, then the ExampleStateMachine future will yield back to the runtime by returning Poll::Pending.

So the translation of your three bulleted points is the following:

  • This refers to the fact that the ExampleStateMachine does not need to return from the call to poll on the enum itself every time it reaches an .await.
  • This refers to the fact that when it calls poll on foo_txt_future or bar_txt_future, the sub-futures can decide to yield to the runtime by returning Poll::Pending, but they are not required to do so — if they return Poll::Ready, then the outer future will not yield back to the runtime.
  • This means that .await compiles down to "the outer future's poll function will call poll on the inner future" as opposed to "spawn the inner future on the runtime and return here when the inner future completes".
3 Likes

What you show is just a compiler bug/limitation with the features. Add 'static to I gets it to compile.

Usefulness on the other hand;
fn next(&mut self) -> Self::NextFuture<'_>
This only allows one call at a single time until the return value finishes.

I have tried a huge variety of hacks to see if I could circumvent this, to no effect: the hidden impl Trait type definition the compiler generates seems unable to carry the bounds that are around our definition of it, so that we get those ": 'a bound unmet" errors.

The following would be a minimal repro:

#![feature(type_alias_impl_trait)]

trait Trait<'a> {
    type Assoc;
    
    fn m(self) -> Self::Assoc;
}

impl<'a, T> Trait<'a> for &'a T {
    type Assoc = impl Sized;
    
    fn m(self: &'a T) -> Self::Assoc { self }
}

Changing the implicit T : 'a bound (from &'a T) to make it explicit fixes the issue, but then any kind of GAT / higher-order reasoning breaks because of that change (the bounds need to remain implicit for things to otherwise work). Hence the conundrum:

  • generic_associated_types (or its stable Rust polyfill) uses (relies on) implicit bounds;

  • type_alias_impl_trait is incompatible with implicit bounds, since it is unable to propagate such bounds to its internal / private helper definition.

    • That being said, I must admit I'm surprised with that + Send changing things (or rather, the lack thereof making things work), when not using the stable-GAT-polyfill: maybe that specific use case has been hard-coded to make it work, but then a specific auto-trait-related extra logic still runs into the issue? :person_shrugging:

So, as disheartening as it may be, you may need to give in and use Pin<Box<dyn-ing, or have that I : 'static. I hope I'm wrong though (cc @quinedot @steffahn, can you have a look at this as well?).

3 Likes

I haven't got much time rn, but as I got pinged, I looked at your minimal repro and came up with this (haven't tried if the same works in the original example): Rust Playground

1 Like

Looks like it doesn't, at least I cannot get it to work quickly. This seems potentially similar to this issue.

4 Likes