Skip to content

Commit 71aef40

Browse files
committed
Move the accidental interconversion support to an alternative, given crater results.
1 parent 589d2e8 commit 71aef40

File tree

1 file changed

+87
-45
lines changed

1 file changed

+87
-45
lines changed

text/0000-try-trait-v2.md

Lines changed: 87 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ Replace [RFC #1859, `try_trait`](https://rust-lang.github.io/rfcs/1859-try-trait
1010
with a new design for the currently-unstable [`Try` trait](https://doc.rust-lang.org/nightly/std/ops/trait.Try.html)
1111
and corresponding desugaring for the `?` operator.
1212

13-
The new design supports all the currently-stable conversions (including the accidental ones),
14-
while addressing the discovered shortcomings of the currently-implemented solution,
15-
as well as enabling new scenarios.
13+
The new design includes support for all *intentional* interconversions.
14+
It proposes removing the *accidental* interconversions, as a crater run
15+
demonstrated that would be feasible, however includes an alternative system
16+
that can support them as a low-support-cost edition mechanism if needed.
1617

1718
*This is [forward-looking](#future-possibilities) to be compatible with other features,
1819
like [`try {}`](https://doc.rust-lang.org/nightly/unstable-book/language-features/try-blocks.html) blocks
@@ -27,7 +28,7 @@ The motivations from the previous RFC still apply (supporting more types, and re
2728
However, new information has come in since the previous RFC, making people wish for a different approach.
2829

2930
- Using the "error" terminology is a poor fit for other potential implementations of the trait.
30-
- The ecosystem has started to see error types which are `From`-convertible from *any* type implementing `Debug`, which makes the previous RFC's method for controlling interconversions ineffective.
31+
- The previous RFC's mechanism for controlling interconversions proved ineffective, with inference meaning that people did it unintentionally.
3132
- It's no longer clear that `From` should be part of the `?` desugaring for _all_ types. It's both more flexible -- making inference difficult -- and more restrictive -- especially without specialization -- than is always desired.
3233
- An [experience report](https://github.com/rust-lang/rust/issues/42327#issuecomment-366840247) in the tracking issue mentioned that it's annoying to need to make a residual type in common cases.
3334

@@ -554,34 +555,6 @@ impl<B, C> ops::FromResidual for ControlFlow<B, C> {
554555
}
555556
```
556557

557-
## Making the accidental `Option` interconversion continue to work
558-
559-
This is done with an extra implementation:
560-
```rust
561-
mod sadness {
562-
use super::*;
563-
564-
/// This is a remnant of the old `NoneError` which is never going to be stabilized.
565-
/// It's here as a snapshot of an oversight that allowed this to work in the past,
566-
/// so we're stuck supporting it even though we'd really rather not.
567-
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
568-
pub struct PleaseCallTheOkOrMethodToUseQuestionMarkOnOptionsInAMethodThatReturnsResult;
569-
570-
impl<T, E> ops::FromResidual<Option<!>> for Result<T, E>
571-
where
572-
E: From<PleaseCallTheOkOrMethodToUseQuestionMarkOnOptionsInAMethodThatReturnsResult>,
573-
{
574-
fn from_residual(x: Option<!>) -> Self {
575-
match x {
576-
None => Err(From::from(
577-
PleaseCallTheOkOrMethodToUseQuestionMarkOnOptionsInAMethodThatReturnsResult,
578-
)),
579-
}
580-
}
581-
}
582-
}
583-
```
584-
585558
## Use in `Iterator`
586559

587560
The provided implementation of `try_fold` is already just using `?` and `try{}`, so doesn't change. The only difference is the name of the associated type in the bound:
@@ -855,14 +828,6 @@ In those cases where a separate type *is* needed, it's still easier to make a re
855828

856829
This RFC uses `!` to be concise. It would work fine with `convert::Infallible` instead if `!` has not yet stabilized, though a few more match arms would be needed in the implementations. (For example, `Option::from_residual` would need `Some(c) => match c {}`.)
857830

858-
## Moving away from the `Option``Result` interconversion
859-
860-
We could consider doing an edition switch to make this no longer allowed.
861-
862-
For example, we could have a different, never-stable `Try`-like trait used in old editions for the `?` desugaring. It could then have a blanket impl, plus the extra interconversion one.
863-
864-
It's unclear that that's worth the effort, however, so this RFC is currently written to continue to support it going forward. Notably, removing it isn't enough to solve the annotation requirements, so the opportunity cost feels low.
865-
866831
## Why `FromResidual` is the supertrait
867832

868833
It's nicer for `try_fold` implementations to just mention the simpler `Try` name. It being the subtrait means that code needing only the basic scenario can just bound on `Try` and know that both `from_output` and `from_residual` are available.
@@ -890,6 +855,85 @@ impl<R: std::fmt::Debug> FromResidual<R> for LogAndIgnoreErrors {
890855

891856
And, ignoring the coherence implications, a major difference between the two sides is that the target type is typically typed out visibly (in a return type) whereas the source type (going into the `?`) is often the result of some called function. So it's preferable for any behaviour extensions to be on the type that can more easily be seen in the code.
892857

858+
## Can we just remove the accidental interconversions?
859+
860+
This depends on how we choose to read the rules around breaking changes.
861+
862+
A [crater run on a prototype implementation](https://github.com/rust-lang/rust/pull/82322#issuecomment-792299734) found that some people are doing this. PRs have been sent to the places that broke, and generally it was agreed that removing the mixing improved the code:
863+
864+
> Definitely a good change.
865+
866+
> Thanks for spotting that, that was indeed a confusing mix
867+
868+
However another instance is in an abandoned project where the repository has been archived, so will not be fixed. And of course if it happened 3 times, there might be more instances in the wild.
869+
870+
The interesting pattern boils down to this:
871+
872+
```rust
873+
.map(|v| Ok(something_returning_option(v)?))
874+
```
875+
876+
That means it's using `?` on an `Option`, but the closure ends up returning `Result<_, NoneError>` without needing to name the type as trait resolution discovers that it's the only possibility. It seems reasonable that this could happen accidentally while refactoring. That does mean, however, that the breakage could also be considered "allowed" as an inference change, and hypothetically additional implementations could make it ambiguous in the future. (It's like the normal `AsRef` breakage, and fits the pattern of "there's a way it could be written that works before and after", though in this case the disambiguated form requires naming an unstable type.)
877+
878+
This RFC thus proposes removing the accidental interconversions.
879+
880+
### Compatibility with accidental interconversions (if needed)
881+
882+
If something happens that turns out they need to be supported, the following approach can work.
883+
884+
This would take a multi-step approach:
885+
- Add a new never-stable `FromResidualLegacy` trait
886+
- Have a blanket implementation so that users interact only with `FromResidual`
887+
- Add implementations for the accidental interconversions
888+
- Use `FromResidualLegacy` in the desugaring, [perhaps only for old editions](https://github.com/scottmcm/rust/commit/do-or-do-not-edition)
889+
890+
This keeps them from being visible in the trait system on stable, as `FromResidual` (the only form that would ever stabilize, or even be mentionable) would not include them.
891+
892+
```rust
893+
mod sadness {
894+
use super::*;
895+
896+
/// This includes all of the [`ops::FromResidual`] conversions, but
897+
/// also adds the two interconversions that work in 2015 & 2018.
898+
/// It will never be stable.
899+
pub trait FromResidualLegacy<R> {
900+
fn from_residual_legacy(r: R) -> Self;
901+
}
902+
903+
impl<T: ops::FromResidual<R>, R> FromResidualLegacy<R> for T {
904+
fn from_residual_legacy(r: R) -> Self {
905+
<Self as ops::FromResidual<R>>::from_residual(r)
906+
}
907+
}
908+
909+
/// This is a remnant of the old `NoneError` which is never going to be stabilized.
910+
/// It's here as a snapshot of an oversight that allowed this to work in the past,
911+
/// so we're stuck supporting it even though we'd really rather not.
912+
/// This will never be stabilized; use [`Option::ok_or`] to mix `Option` and `Result`.
913+
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
914+
pub struct LegacyNoneError;
915+
916+
impl<T, E> ops::FromResidual<Option<!>> for Result<T, E>
917+
where
918+
E: From<LegacyNoneError>,
919+
{
920+
fn from_residual(x: Option<!>) -> Self {
921+
match x {
922+
None => Err(From::from(LegacyNoneError)),
923+
}
924+
}
925+
}
926+
927+
928+
#[unstable(feature = "try_trait_v2", issue = "42327")]
929+
impl<T> FromResidualLegacy<Result<!, LegacyNoneError>> for Option<T>
930+
{
931+
fn from_residual_legacy(_: Result<!, LegacyNoneError>) -> Self {
932+
None
933+
}
934+
}
935+
}
936+
```
893937

894938

895939
<!--
@@ -947,19 +991,17 @@ Please also take into consideration that rust sometimes intentionally diverges f
947991
# Unresolved questions
948992
[unresolved-questions]: #unresolved-questions
949993

950-
Waiting on crater:
951-
- [ ] Find out more about how widespread the accidental interconversions are, and change the RFC to propose whether to bless/lint/remove them, and how.
952-
953994
Questions from T-libs to be resolved in nightly:
954995
- [ ] What vocabulary should `Try` use in the associated types/traits? Output+residual, continue+break, or something else entirely?
955996
- [ ] Is it ok for the two traits to be tied together closely, as outlined here, or should they be split up further to allow types that can be only-created or only-destructured?
956997

957998
## Implementation and Stabilization Sequencing
958999

9591000
- `ControlFlow` is implemented in nightly already.
960-
- The traits and desugaring could go into nightly (assuming with a confirming crater run) immediately, with implementations for the existing `Option`<->`Result` interconversions.
1001+
- The traits and desugaring could go into nightly immediately.
9611002
- That would allow `ControlFlow` to be considered for stabilizating, as the new desugaring would keep from stabilizing any unwanted interconversions.
962-
- Then the unresolved questions need to be addressed before `Try` could stabilize.
1003+
- Beta testing might result in reports requiring that the accidental interconversions be added back in old editions, due to crater-invisible code.
1004+
- Then the unresolved naming & structure questions need to be addressed before `Try` could stabilize.
9631005

9641006
<!--
9651007
- What parts of the design do you expect to resolve through the RFC process before this gets merged?

0 commit comments

Comments
 (0)