Description
// 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.
- 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?