diff --git a/library/core/src/ops/control_flow.rs b/library/core/src/ops/control_flow.rs index 2f78ba8f28e29..ecaff053bd5c0 100644 --- a/library/core/src/ops/control_flow.rs +++ b/library/core/src/ops/control_flow.rs @@ -1,4 +1,5 @@ -use crate::ops::Try; +use crate::convert; +use crate::ops::{self, Try}; /// Used to tell an operation whether it should exit early or go on as usual. /// @@ -81,6 +82,35 @@ impl Try for ControlFlow { } } +#[unstable(feature = "try_trait_v2", issue = "84277")] +impl ops::TryV2 for ControlFlow { + type Output = C; + type Residual = ControlFlow; + + #[inline] + fn from_output(output: Self::Output) -> Self { + ControlFlow::Continue(output) + } + + #[inline] + fn branch(self) -> ControlFlow { + match self { + ControlFlow::Continue(c) => ControlFlow::Continue(c), + ControlFlow::Break(b) => ControlFlow::Break(ControlFlow::Break(b)), + } + } +} + +#[unstable(feature = "try_trait_v2", issue = "84277")] +impl ops::FromResidual for ControlFlow { + #[inline] + fn from_residual(residual: ControlFlow) -> Self { + match residual { + ControlFlow::Break(b) => ControlFlow::Break(b), + } + } +} + impl ControlFlow { /// Returns `true` if this is a `Break` variant. /// diff --git a/library/core/src/ops/mod.rs b/library/core/src/ops/mod.rs index 354ad6b7b7333..1b07936ccde1d 100644 --- a/library/core/src/ops/mod.rs +++ b/library/core/src/ops/mod.rs @@ -148,6 +148,7 @@ mod generator; mod index; mod range; mod r#try; +mod try_trait; mod unsize; #[stable(feature = "rust1", since = "1.0.0")] @@ -184,6 +185,12 @@ pub use self::range::{Bound, RangeBounds, RangeInclusive, RangeToInclusive}; #[unstable(feature = "try_trait", issue = "42327")] pub use self::r#try::Try; +#[unstable(feature = "try_trait_v2", issue = "84277")] +pub use self::try_trait::FromResidual; + +#[unstable(feature = "try_trait_transition", reason = "for bootstrap", issue = "none")] +pub use self::try_trait::Try as TryV2; + #[unstable(feature = "generator_trait", issue = "43122")] pub use self::generator::{Generator, GeneratorState}; diff --git a/library/core/src/ops/try_trait.rs b/library/core/src/ops/try_trait.rs new file mode 100644 index 0000000000000..0c819b000aaab --- /dev/null +++ b/library/core/src/ops/try_trait.rs @@ -0,0 +1,243 @@ +use crate::ops::ControlFlow; + +/// The `?` operator and `try {}` blocks. +/// +/// `try_*` methods typically involve a type implementing this trait. For +/// example, the closures passed to [`Iterator::try_fold`] and +/// [`Iterator::try_for_each`] must return such a type. +/// +/// `Try` types are typically those containing two or more categories of values, +/// some subset of which are so commonly handled via early returns that it's +/// worth providing a terse (but still visible) syntax to make that easy. +/// +/// This is most often seen for error handling with [`Result`] and [`Option`]. +/// The quintessential implementation of this trait is on [`ControlFlow`]. +/// +/// # Using `Try` in Generic Code +/// +/// `Iterator::try_fold` was stabilized to call back in Rust 1.27, but +/// this trait is much newer. To illustrate the various associated types and +/// methods, let's implement our own version. +/// +/// As a reminder, an infallible version of a fold looks something like this: +/// ``` +/// fn simple_fold( +/// iter: impl Iterator, +/// mut accum: A, +/// mut f: impl FnMut(A, T) -> A, +/// ) -> A { +/// for x in iter { +/// accum = f(accum, x); +/// } +/// accum +/// } +/// ``` +/// +/// So instead of `f` returning just an `A`, we'll need it to return some other +/// type that produces an `A` in the "don't short circuit" path. Conveniently, +/// that's also the type we need to return from the function. +/// +/// Let's add a new generic parameter `R` for that type, and bound it to the +/// output type that we want: +/// ``` +/// # #![feature(try_trait_v2)] +/// # #![feature(try_trait_transition)] +/// # use std::ops::TryV2 as Try; +/// fn simple_try_fold_1>( +/// iter: impl Iterator, +/// mut accum: A, +/// mut f: impl FnMut(A, T) -> R, +/// ) -> R { +/// todo!() +/// } +/// ``` +/// +/// If we get through the entire iterator, we need to wrap up the accumulator +/// into the return type using [`Try::from_output`]: +/// ``` +/// # #![feature(try_trait_v2)] +/// # #![feature(try_trait_transition)] +/// # #![feature(control_flow_enum)] +/// # use std::ops::{ControlFlow, TryV2 as Try}; +/// fn simple_try_fold_2>( +/// iter: impl Iterator, +/// mut accum: A, +/// mut f: impl FnMut(A, T) -> R, +/// ) -> R { +/// for x in iter { +/// let cf = f(accum, x).branch(); +/// match cf { +/// ControlFlow::Continue(a) => accum = a, +/// ControlFlow::Break(_) => todo!(), +/// } +/// } +/// R::from_output(accum) +/// } +/// ``` +/// +/// We'll also need [`FromResidual::from_residual`] to turn the residual back +/// into the original type. But because it's a supertrait of `Try`, we don't +/// need to mention it in the bounds. All types which implement `Try` can be +/// recreated from their corresponding residual, so we'll just call it: +/// ``` +/// # #![feature(try_trait_v2)] +/// # #![feature(try_trait_transition)] +/// # #![feature(control_flow_enum)] +/// # use std::ops::{ControlFlow, TryV2 as Try}; +/// pub fn simple_try_fold_3>( +/// iter: impl Iterator, +/// mut accum: A, +/// mut f: impl FnMut(A, T) -> R, +/// ) -> R { +/// for x in iter { +/// let cf = f(accum, x).branch(); +/// match cf { +/// ControlFlow::Continue(a) => accum = a, +/// ControlFlow::Break(r) => return R::from_residual(r), +/// } +/// } +/// R::from_output(accum) +/// } +/// ``` +/// +/// But this "call `branch`, then `match` on it, and `return` if it was a +/// `Break`" is exactly what happens inside the `?` operator. So rather than +/// do all this manually, we can just use `?` instead: +/// ```compile_fail (enable again once ? converts to the new trait) +/// # #![feature(try_trait_v2)] +/// # #![feature(try_trait_transition)] +/// # use std::ops::TryV2 as Try; +/// fn simple_try_fold>( +/// iter: impl Iterator, +/// mut accum: A, +/// mut f: impl FnMut(A, T) -> R, +/// ) -> R { +/// for x in iter { +/// accum = f(accum, x)?; +/// } +/// R::from_output(accum) +/// } +/// ``` +#[unstable(feature = "try_trait_v2", issue = "84277")] +pub trait Try: FromResidual { + /// The type of the value produced by `?` when *not* short-circuiting. + #[unstable(feature = "try_trait_v2", issue = "84277")] + type Output; + + /// The type of the value passed to [`FromResidual::from_residual`] + /// as part of `?` when short-circuiting. + /// + /// This represents the possible values of the `Self` type which are *not* + /// represented by the `Output` type. + /// + /// # Note to Implementors + /// + /// The choice of this type is critical to interconversion. + /// Unlike the `Output` type, which will often be a raw generic type, + /// this type is typically a newtype of some sort to "color" the type + /// so that it's distinguishable from the residuals of other types. + /// + /// This is why `Result::Residual` is not `E`, but `Result`. + /// That way it's distinct from `ControlFlow::Residual`, for example, + /// and thus `?` on `ControlFlow` cannot be used in a method returning `Result`. + /// + /// If you're making a generic type `Foo` that implements `Try`, + /// then typically you can use `Foo` as its `Residual` + /// type: that type will have a "hole" in the correct place, and will maintain the + /// "foo-ness" of the residual so other types need to opt-in to interconversion. + #[unstable(feature = "try_trait_v2", issue = "84277")] + type Residual; + + /// Constructs the type from its `Output` type. + /// + /// This should be implemented consistently with the `branch` method + /// such that applying the `?` operator will get back the original value: + /// `Try::from_output(x).branch() --> ControlFlow::Continue(x)`. + /// + /// # Examples + /// + /// ``` + /// #![feature(try_trait_v2)] + /// #![feature(control_flow_enum)] + /// #![feature(try_trait_transition)] + /// use std::ops::TryV2 as Try; + /// + /// assert_eq!( as Try>::from_output(3), Ok(3)); + /// assert_eq!( as Try>::from_output(4), Some(4)); + /// assert_eq!( + /// as Try>::from_output(5), + /// std::ops::ControlFlow::Continue(5), + /// ); + /// + /// # fn make_question_mark_work() -> Option<()> { + /// assert_eq!(Option::from_output(4)?, 4); + /// # None } + /// # make_question_mark_work(); + /// + /// // This is used, for example, on the accumulator in `try_fold`: + /// let r = std::iter::empty().try_fold(4, |_, ()| -> Option<_> { unreachable!() }); + /// assert_eq!(r, Some(4)); + /// ``` + #[unstable(feature = "try_trait_v2", issue = "84277")] + fn from_output(output: Self::Output) -> Self; + + /// Used in `?` to decide whether the operator should produce a value + /// (because this returned [`ControlFlow::Continue`]) + /// or propagate a value back to the caller + /// (because this returned [`ControlFlow::Break`]). + /// + /// # Examples + /// + /// ``` + /// #![feature(try_trait_v2)] + /// #![feature(control_flow_enum)] + /// #![feature(try_trait_transition)] + /// use std::ops::{ControlFlow, TryV2 as Try}; + /// + /// assert_eq!(Ok::<_, String>(3).branch(), ControlFlow::Continue(3)); + /// assert_eq!(Err::(3).branch(), ControlFlow::Break(Err(3))); + /// + /// assert_eq!(Some(3).branch(), ControlFlow::Continue(3)); + /// assert_eq!(None::.branch(), ControlFlow::Break(None)); + /// + /// assert_eq!(ControlFlow::::Continue(3).branch(), ControlFlow::Continue(3)); + /// assert_eq!( + /// ControlFlow::<_, String>::Break(3).branch(), + /// ControlFlow::Break(ControlFlow::Break(3)), + /// ); + /// ``` + #[unstable(feature = "try_trait_v2", issue = "84277")] + fn branch(self) -> ControlFlow; +} + +/// Used to specify which residuals can be converted into which [`Try`] types. +/// +/// Every `Try` type needs to be recreatable from its own associated +/// `Residual` type, but can also have additional `FromResidual` implementations +/// to support interconversion with other `Try` types. +#[unstable(feature = "try_trait_v2", issue = "84277")] +pub trait FromResidual::Residual> { + /// Constructs the type from a compatible `Residual` type. + /// + /// This should be implemented consistently with the `branch` method such + /// that applying the `?` operator will get back an equivalent residual: + /// `FromResidual::from_residual(r).branch() --> ControlFlow::Break(r)`. + /// (It may not be an *identical* residual when interconversion is involved.) + /// + /// # Examples + /// + /// ``` + /// #![feature(try_trait_v2)] + /// #![feature(control_flow_enum)] + /// use std::ops::{ControlFlow, FromResidual}; + /// + /// assert_eq!(Result::::from_residual(Err(3_u8)), Err(3)); + /// assert_eq!(Option::::from_residual(None), None); + /// assert_eq!( + /// ControlFlow::<_, String>::from_residual(ControlFlow::Break(5)), + /// ControlFlow::Break(5), + /// ); + /// ``` + #[unstable(feature = "try_trait_v2", issue = "84277")] + fn from_residual(residual: R) -> Self; +} diff --git a/library/core/src/option.rs b/library/core/src/option.rs index 1c68abaf79d23..9c527eff4916f 100644 --- a/library/core/src/option.rs +++ b/library/core/src/option.rs @@ -150,8 +150,8 @@ use crate::iter::{FromIterator, FusedIterator, TrustedLen}; use crate::pin::Pin; use crate::{ - hint, mem, - ops::{self, Deref, DerefMut}, + convert, hint, mem, + ops::{self, ControlFlow, Deref, DerefMut}, }; /// The `Option` type. See [the module level documentation](self) for more. @@ -1664,6 +1664,35 @@ impl ops::Try for Option { } } +#[unstable(feature = "try_trait_v2", issue = "84277")] +impl ops::TryV2 for Option { + type Output = T; + type Residual = Option; + + #[inline] + fn from_output(output: Self::Output) -> Self { + Some(output) + } + + #[inline] + fn branch(self) -> ControlFlow { + match self { + Some(v) => ControlFlow::Continue(v), + None => ControlFlow::Break(None), + } + } +} + +#[unstable(feature = "try_trait_v2", issue = "84277")] +impl ops::FromResidual for Option { + #[inline] + fn from_residual(residual: Option) -> Self { + match residual { + None => None, + } + } +} + impl Option> { /// Converts from `Option>` to `Option` /// diff --git a/library/core/src/result.rs b/library/core/src/result.rs index 20f8095b7d1ce..bac02104c3488 100644 --- a/library/core/src/result.rs +++ b/library/core/src/result.rs @@ -228,7 +228,7 @@ #![stable(feature = "rust1", since = "1.0.0")] use crate::iter::{self, FromIterator, FusedIterator, TrustedLen}; -use crate::ops::{self, Deref, DerefMut}; +use crate::ops::{self, ControlFlow, Deref, DerefMut}; use crate::{convert, fmt, hint}; /// `Result` is a type that represents either success ([`Ok`]) or failure ([`Err`]). @@ -1646,3 +1646,32 @@ impl ops::Try for Result { Err(v) } } + +#[unstable(feature = "try_trait_v2", issue = "84277")] +impl ops::TryV2 for Result { + type Output = T; + type Residual = Result; + + #[inline] + fn from_output(output: Self::Output) -> Self { + Ok(output) + } + + #[inline] + fn branch(self) -> ControlFlow { + match self { + Ok(v) => ControlFlow::Continue(v), + Err(e) => ControlFlow::Break(Err(e)), + } + } +} + +#[unstable(feature = "try_trait_v2", issue = "84277")] +impl> ops::FromResidual> for Result { + #[inline] + fn from_residual(residual: Result) -> Self { + match residual { + Err(e) => Err(From::from(e)), + } + } +} diff --git a/library/core/src/task/poll.rs b/library/core/src/task/poll.rs index 42c9d9f0cc039..2765c21a46db1 100644 --- a/library/core/src/task/poll.rs +++ b/library/core/src/task/poll.rs @@ -1,6 +1,7 @@ #![stable(feature = "futures_api", since = "1.36.0")] -use crate::ops::Try; +use crate::convert; +use crate::ops::{self, ControlFlow, Try}; use crate::result::Result; /// Indicates whether a value is available or if the current task has been @@ -152,6 +153,36 @@ impl Try for Poll> { } } +#[unstable(feature = "try_trait_v2", issue = "84277")] +impl ops::TryV2 for Poll> { + type Output = Poll; + type Residual = Result; + + #[inline] + fn from_output(c: Self::Output) -> Self { + c.map(Ok) + } + + #[inline] + fn branch(self) -> ControlFlow { + match self { + Poll::Ready(Ok(x)) => ControlFlow::Continue(Poll::Ready(x)), + Poll::Ready(Err(e)) => ControlFlow::Break(Err(e)), + Poll::Pending => ControlFlow::Continue(Poll::Pending), + } + } +} + +#[unstable(feature = "try_trait_v2", issue = "84277")] +impl> ops::FromResidual> for Poll> { + #[inline] + fn from_residual(x: Result) -> Self { + match x { + Err(e) => Poll::Ready(Err(From::from(e))), + } + } +} + #[stable(feature = "futures_api", since = "1.36.0")] impl Try for Poll>> { type Ok = Poll>; @@ -177,3 +208,36 @@ impl Try for Poll>> { x.map(|x| x.map(Ok)) } } + +#[unstable(feature = "try_trait_v2", issue = "84277")] +impl ops::TryV2 for Poll>> { + type Output = Poll>; + type Residual = Result; + + #[inline] + fn from_output(c: Self::Output) -> Self { + c.map(|x| x.map(Ok)) + } + + #[inline] + fn branch(self) -> ControlFlow { + match self { + Poll::Ready(Some(Ok(x))) => ControlFlow::Continue(Poll::Ready(Some(x))), + Poll::Ready(Some(Err(e))) => ControlFlow::Break(Err(e)), + Poll::Ready(None) => ControlFlow::Continue(Poll::Ready(None)), + Poll::Pending => ControlFlow::Continue(Poll::Pending), + } + } +} + +#[unstable(feature = "try_trait_v2", issue = "84277")] +impl> ops::FromResidual> + for Poll>> +{ + #[inline] + fn from_residual(x: Result) -> Self { + match x { + Err(e) => Poll::Ready(Some(Err(From::from(e)))), + } + } +}