Skip to content

Weirdness around async function with error "implementation of FnOnce is not general enough" #114177

Open
@e00E

Description

@e00E
// Using `impl Future` instead of `async to ensure that the Future is Send.
//
// In the original code `a` would be `&[T]`. For more minimization I've removed the reference.
fn foo(a: [(); 0]) -> impl std::future::Future<Output = ()> + Send {
    async move {
        let iter = Adaptor::new(a.iter().map(|_: &()| {}));
        std::future::pending::<()>().await;
    }
}

struct Adaptor<T: Iterator> {
    iter: T,
    v: T::Item,
}

impl<T: Iterator> Adaptor<T> {
    pub fn new(_: T) -> Self {
        Self {
            iter: todo!(),
            v: todo!(),
        }
    }
}

This code is the result of minimizing a compiler error in an async function using Itertools::unique. Compiling this code is weird in several ways:

1:
Compilation fails with this message:

error: implementation of `Iterator` is not general enough
 --> src/lib.rs:5:5
  |
5 | /     async move {
6 | |         let iter = Adaptor::new(a.iter().map(|_: &()| {}));
7 | |         std::future::pending::<()>().await;
8 | |     }
  | |_____^ implementation of `Iterator` is not general enough
  |
  = note: `Iterator` would have to be implemented for the type `std::slice::Iter<'0, ()>`, for any lifetime `'0`...
  = note: ...but `Iterator` is actually implemented for the type `std::slice::Iter<'1, ()>`, for some specific lifetime `'1`

error: implementation of `FnOnce` is not general enough
 --> src/lib.rs:5:5
  |
5 | /     async move {
6 | |         let iter = Adaptor::new(a.iter().map(|_: &()| {}));
7 | |         std::future::pending::<()>().await;
8 | |     }
  | |_____^ implementation of `FnOnce` is not general enough
  |
  = note: closure with signature `fn(&'0 ())` must implement `FnOnce<(&'1 (),)>`, for any two lifetimes `'0` and `'1`...
  = note: ...but it actually implements `FnOnce<(&(),)>`

This message is confusing. Why are there two lifetimes? What does the "actually implements" line mean? How can I fix it?

2:
The code compiles when removing the Send bound. This is confusing because the error message does not mention Send. Why does the lifetime error message cause the Future to not be Send? Why do the following changes to the code fix the lifetime error and make the Future Send?

3:
The code can be made to compile by moving the inline closure into a separate variable:

        let f = |_: &()| {};
        // or
        fn f(_: &()) {}
        let iter = Adaptor::new(a.iter().map(f));

This is surprising. Why does inlining the definition of the closure cause the code to not compile?

4:
The code can be made to compile by not holding the iterator across the await point:

        {
            let iter = Adaptor::new(a.iter().map(|_: &()| {}));
        }
        std::future::pending::<()>().await;

This is surprising because the error message does not indicate the lifetime of the iterator itself is a problem.

  1. Trying to do the same thing by dropping the iterator does not work.
        let iter = Adaptor::new(a.iter().map(|_: &()| {}));
        std::mem::drop(iter);
        std::future::pending::<()>().await;

Why does it make a difference whether there is a {} block or a manual drop?

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-async-awaitArea: Async & AwaitAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.C-bugCategory: This is a bug.T-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions