You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Tracking Issue for ops::Residual (feature try_trait_v2_residual) #91285
For example, the closure passed to Iterator::try_find returns Foo<bool>, but the method wants to be able to return Foo<Option<<Self as Iterator>::Item>>.
Public API
// ops::ResidualtraitResidual<O>{typeTryTrait:Try<Output = O,Residual = Self>;}// with implementations for `Result`, `Option`, and `ControlFlow`.
Notice that : Residual<[R::Output; N]> clause. It can't really be spelled out/read out loud in English: "is a residual of an array / can residual an array" doesn't make that much sense.
In a way, the type alias internally used by the stdlib already provides a way nicer name: ChangeOutputType.
So in this case, taking that name and using it on the trait itself, we would rather imagine:
R::Residual:CanWrapOutputType<[R::Output;N]>,
(or {Feed,Apply,Change,Set,With}OutputType)
Now we get something that does read better, although it talks a bit too much about type-level operations, which is something the stdlib doesn't do that often.
So, rather than focusing on the how, if we focus on the what instead, we can stick to the "being a Residual" property, but this time with no generic Output parameter yet, by delegating it to a GAT, which features the proper quantification of "it can be fed any output type":
R::Residual:Residual,// <- Bonus: this wouldn't even be needed, since now we could eagerly add this bound to `Try`!
and then using <R::Residual as Residual>::TryType<[T; N]>.
Or even TryTypeWithOutput<[T; N]>. I'll be using this new name for the remainder of the post
Notice that bonus of being able to eagerly require that Try::Residual types always implement Residual, which we can't do with the current design since that would need a for<Output> kind of quantification. EDIT: not all Residuals / Try types may want to be able to wrap any kind of T, as pointed out by @h4x5 in the comment just below.
I think it would be confusing to have some Try types not be usable with array_from_fn just because of the Residual associated type happens not to meet part of the implicitly required API contract. Having it explicitly required seems like a definite win, in that regard.
A technical remark, however, @h4x5: we can't use Res : Residual and then an impl Fn… -> Res::TryTypeWithOutput<T> closure, and then expect Res to be inferrable from context. Indeed, we'd have a "preïmage" situation regarding the type FeedT<Res> = Res::TryType<T>; operation, which is not an injective one, and thus won't be solvable by type inference.
So, while keeping Res seems convenient for the sake of the signature (Res::TryType<…>), we'd have to instead say that the output of the closure implements Try<Residual = R>:
Oh, good catch! (I've edited my post accordingly).
🤔 In that case I very much feel like that trait ought not to be named Residual. Given R : Try, then having R::Residual : Residual not hold by default and have to be explicitly spelled out feels quite weird, tbh. We'd be back to ChangeOutput or things along those lines, etc.
The thing I'm worried about using a GAT here is that it makes a bunch of things not work that would work without the GAT. The problem is that the GAT is the equivalent of a for<T> R: Residual<T>. But that's a very strong promise. It means, for example, that you wouldn't be able to have a Try type that would work only with Output types that are Copy.
Imagine a Rust type for an HRESULT. That might look something like this:
But that Residual impl can't turn into a GAT, because it's not ∀T, but only for someTs. And there's no syntax that exists to restrict the GAT in an impl -- the restrictions need to be in the trait, where there wouldn't be any for this.
Thoughts?
Reacted by Ben Reeves, Daniel Henry-Mantilla and David Tolnay
It is because the Try implementations for Poll<Result<T, E>> and Poll<Option<Result<T, E>>> are weird also. Their Residuals are both Result<Infallible, E>, which causes the weirdness. But since the implementations are exposed for stable, I think we can only do something withResidual trait? Or the two try_ methods/functions?
Activity
BGR360 commentedon Dec 26, 2021
@rustbot label +F-try_trait_v2
danielhenrymantilla commentedon Nov 7, 2022
Yeah, the current
Residualtrait reads rather awkwardly due to that genericOparameter it must be fed eagerly:Notice that
: Residual<[R::Output; N]>clause. It can't really be spelled out/read out loud in English: "is a residual of an array / can residual an array" doesn't make that much sense.In a way, the type alias internally used by the stdlib already provides a way nicer name:
ChangeOutputType.So in this case, taking that name and using it on the trait itself, we would rather imagine:
{Feed,Apply,Change,Set,With}OutputType)Now we get something that does read better, although it talks a bit too much about type-level operations, which is something the stdlib doesn't do that often.
So, rather than focusing on the how, if we focus on the what instead, we can stick to the "being a
Residual" property, but this time with no genericOutputparameter yet, by delegating it to a GAT, which features the proper quantification of "it can be fed any output type":and then using
<R::Residual as Residual>::TryType<[T; N]>.TryTypeWithOutput<[T; N]>. I'll be using this new name for the remainder of the postNotice that bonus of being able to eagerly require thatEDIT: not allTry::Residualtypes always implementResidual, which we can't do with the current design since that would need afor<Output>kind of quantification.Residuals /Trytypes may want to be able to wrap any kind ofT, as pointed out by @h4x5 in the comment just below.I think it would be confusing to have some
Trytypes not be usable witharray_from_fnjust because of theResidualassociated type happens not to meet part of the implicitly required API contract. Having it explicitly required seems like a definite win, in that regard.A technical remark, however, @h4x5: we can't use
Res : Residualand then animpl Fn… -> Res::TryTypeWithOutput<T>closure, and then expectResto be inferrable from context. Indeed, we'd have a "preïmage" situation regarding thetype FeedT<Res> = Res::TryType<T>;operation, which is not an injective one, and thus won't be solvable by type inference.So, while keeping
Resseems convenient for the sake of the signature (Res::TryType<…>), we'd have to instead say that the output of the closure implementsTry<Residual = R>:RettoRet::Residualin order to figure outRes.Or we could get rid of that
Resaltogether:danielhenrymantilla commentedon Nov 7, 2022
Oh, good catch! (I've edited my post accordingly).
Residual. GivenR : Try, then havingR::Residual : Residualnot hold by default and have to be explicitly spelled out feels quite weird, tbh. We'd be back toChangeOutputor things along those lines, etc.scottmcm commentedon Nov 9, 2022
(See some additional conversation on zulip: https://rust-lang.zulipchat.com/#narrow/stream/219381-t-libs/topic/lib.20after.20GATs/near/308206137)
The thing I'm worried about using a GAT here is that it makes a bunch of things not work that would work without the GAT. The problem is that the GAT is the equivalent of a
for<T> R: Residual<T>. But that's a very strong promise. It means, for example, that you wouldn't be able to have aTrytype that would work only with Output types that areCopy.Imagine a Rust type for an
HRESULT. That might look something like this:https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=93e6c13eea6bf3b93683576d6aa79f3a
But that
Residualimpl can't turn into a GAT, because it's not∀T, but only for someTs. And there's no syntax that exists to restrict the GAT in an impl -- the restrictions need to be in the trait, where there wouldn't be any for this.Thoughts?
discreaminant2809 commentedon Mar 9, 2024
The
Residualtype mapping forPollseems weird for me :It is because the
Tryimplementations forPoll<Result<T, E>>andPoll<Option<Result<T, E>>>are weird also. TheirResiduals are bothResult<Infallible, E>, which causes the weirdness. But since the implementations are exposed for stable, I think we can only do something withResidualtrait? Or the twotry_methods/functions?Rollup merge of rust-lang#149135 - nxsaken:const_residual_into_try_ty…
Rollup merge of rust-lang#149135 - nxsaken:const_residual_into_try_ty…
Unrolled build for #149135
Rollup merge of rust-lang#149135 - nxsaken:const_residual_into_try_ty…
Rollup merge of rust-lang#155142 - nxsaken:const_residual_control_flo…
Unrolled build for #155142