Implementation of trait is not general enough when used inside tokio::spawn
Ah, I finally found a good explanation of the _actual_ underlying issue, too:
### rust-lang/rust#102211 (comment)
danielhenrymantilla:
> Replying to **@emersonford**'s [post here since it seems more appropriate]
>
> * * *
>
> nice post **@emersonford**
>
> Regarding:
>
>> What's not immediately clear to me though is why this is so specific to auto traits + generators. Even if you don't need to prove Send for the generator, don't you still need to prove that Trait is implemented for fn(&'static ()) inside of the generator interior? I.e., why does
>
> It's specific to:
>
> 1. generators, because they're one place where the lifetimes used by the resulting type/enum that makes the `Future` are erased: Rust does not have the tools to express self-referential lifetimes, so to avoid being unsound with the "made up" lifetimes that can end up populating the compiler-generated self-referential datastructure backing generators / `async`, the compiler straight up erases them somehow so as to prevent lifetime-specific impls from applying (since in certain scenarios this could be unsound);
> 2. auto-traits since they:
> * leak through `-> impl Trait` / existential trait abstractions (that is, contrary to other traits which are just stated explicitly as part of the `-> impl Trait` contract, so that they can be checked within the right context, auto-traits are not enforced there and then, and instead checked later on, in a context with erased lifetimes;
> * in a structural manner, meaning it delegates to each generator capture implementing the trait, as you mentioned, which brings us to `1.`
>
>
> * * *
>
> Now, the culprit triggering this is indeed the associated type, `<T as Trait>::Assoc`. At that point, _even if the data structure itself (`Foo` in my example) unconditionally requires `T : Trait`_, the trait checker will nonetheless try to prove this _again_ , and this time with the added difficulty of having erased lifetimes, _even the`'static` lifetime_.
>
> ### Workaround for library authors
>
> Is then not to use associated types.
>
> That is, replacing:
>
>
> struct Foo<T : Trait>(
> T::Assoc,
> );
>
>
> with:
>
>
> struct Foo<T : Trait<Assoc = R>, R = <T as Trait>::Assoc>(
> R,
> ::core::marker::PhantomData<T>,
> );
>
>
> that is, we introduce a new type `R`, which defaults to `T::Assoc`, and we also require that `T::Assoc` be equal to `R`. All in all, inference should work just as well, and basically anything else too, but for the type now containing a fully standalone `R` type. This will avoid the requirement to check `T : Trait` within an erased-lifetime scenario.
>
> #### Illustration
>
> Using `.flat_map(|x| x)` instead of `.flatten()` dodges the issues, even though they are semantically equivalent. The difference is that `flatten` does mention `<I as Iterator>::Item`, whereas `flat_map` uses a fresh `U` type parameter which is only constrained to be `F::Output` for extra trait `impl`ementations.
>
> * * *
>
> ### Potentially interesting?
>
> Since using `struct X(fn(&'static ());` dodges the issue too (since the `X` "type constructor" is no longer a "type with a movable lifetime within in"), I got curious about trying to formalize stuff using hand-rolled type constructors, _i.e._ , `HKT`s:
>
>
> trait WithLifetime<'a> { type T; }
> type Feed<'a, T : WithLifetime<'a>> = T::T;
> trait HKT = for<'a> WithLifetime<'a>;
>
> type A = dyn for<'a> WithLifetime<'a, T = fn(&'static ())>;
> type B = dyn for<'a> WithLifetime<'a, T = fn(&'a ())>;
>
>
> * with macro sugar:
>
> > type A = HKT!(<'a> = fn(&'static ());
> type B = HKT!(<'a> = fn(&'a ());
>
>
>
>
> Here, we have `A` and `B` both being _fully-fledged types_ , but which happen to express a form of `<'a>`-genericity, which could be expressed in the following pseudo-code, sort to speak:
>
>
> type A<'a> = fn(&'static ());
> type B<'a> = fn(&'a ());
>
>
> And doing that, we could then replace `struct Foo<T>(<T as Trait>::Assoc) where T : Trait;` definition with:
>
>
> /// Idea: replace `T` with `Feed<'static, T_>`, for some `T_ : <'_>` "generic generic" type.
> struct Foo<T_ : ?Sized + HKT>(
> <Feed<'static, T_> as Trait>::Assoc,
> )
> where
> Feed<'static, T_> : Trait,
> ;
>
>
> With that, using `Foo::<A>` _fails_ , but using `Foo::<B>` _does not fail_ , even though they both amount to the same `T = fn(&'static ())`.
>
> * I think the distinction stems from `A` "capturing" `'static`, and thus being detected by the lifetime-eraser heuristic,
> * whereas `B` does not capture anything on its own: it's just its associated type which does, but after being queried with a fed `'static` to the `WithLifetime` trait. And somehow _that_ lifetime in `as WithLifetime<'static>` does not get erased
>