rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
97.86k stars 12.67k forks source link

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

Open e00E opened 1 year ago

e00E commented 1 year ago
// 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?

traviscross commented 2 months ago

@rustbot labels +AsyncAwait-Triaged

This is another case of: