#EmersonFord
Thank you, #NetGalley and #StormPublishing for allowing me to read #WhatTheSilentSay by #EmersonFord It’s a WWII novel but in the POV of American soldiers. A war story that captures loves and resilience in worlds oceans apart. It’s beautifully written. I highly recommend! #BookSky #ARC
January 19, 2025 at 4:24 PM
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 >
users.rust-lang.org
December 20, 2024 at 6:01 AM