Skip to content

Commit 1864970

Browse files
committed
Add the try_trait_v2 library basics
No compiler changes as part of this -- just new unstable traits and impls thereof. The goal here is to add the things that aren't going to break anything, to keep the feature implementation simpler in the next PR.
1 parent 361bfce commit 1864970

File tree

6 files changed

+389
-5
lines changed

6 files changed

+389
-5
lines changed

library/core/src/ops/control_flow.rs

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use crate::ops::Try;
1+
use crate::convert;
2+
use crate::ops::{self, Try};
23

34
/// Used to tell an operation whether it should exit early or go on as usual.
45
///
@@ -81,6 +82,35 @@ impl<B, C> Try for ControlFlow<B, C> {
8182
}
8283
}
8384

85+
#[unstable(feature = "try_trait_v2", issue = "84277")]
86+
impl<B, C> ops::TryV2 for ControlFlow<B, C> {
87+
type Output = C;
88+
type Residual = ControlFlow<B, convert::Infallible>;
89+
90+
#[inline]
91+
fn from_output(output: Self::Output) -> Self {
92+
ControlFlow::Continue(output)
93+
}
94+
95+
#[inline]
96+
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
97+
match self {
98+
ControlFlow::Continue(c) => ControlFlow::Continue(c),
99+
ControlFlow::Break(b) => ControlFlow::Break(ControlFlow::Break(b)),
100+
}
101+
}
102+
}
103+
104+
#[unstable(feature = "try_trait_v2", issue = "84277")]
105+
impl<B, C> ops::FromResidual for ControlFlow<B, C> {
106+
#[inline]
107+
fn from_residual(residual: ControlFlow<B, convert::Infallible>) -> Self {
108+
match residual {
109+
ControlFlow::Break(b) => ControlFlow::Break(b),
110+
}
111+
}
112+
}
113+
84114
impl<B, C> ControlFlow<B, C> {
85115
/// Returns `true` if this is a `Break` variant.
86116
///

library/core/src/ops/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ mod generator;
148148
mod index;
149149
mod range;
150150
mod r#try;
151+
mod try_trait;
151152
mod unsize;
152153

153154
#[stable(feature = "rust1", since = "1.0.0")]
@@ -184,6 +185,12 @@ pub use self::range::{Bound, RangeBounds, RangeInclusive, RangeToInclusive};
184185
#[unstable(feature = "try_trait", issue = "42327")]
185186
pub use self::r#try::Try;
186187

188+
#[unstable(feature = "try_trait_v2", issue = "84277")]
189+
pub use self::try_trait::FromResidual;
190+
191+
#[unstable(feature = "try_trait_transition", reason = "for bootstrap", issue = "none")]
192+
pub use self::try_trait::Try as TryV2;
193+
187194
#[unstable(feature = "generator_trait", issue = "43122")]
188195
pub use self::generator::{Generator, GeneratorState};
189196

library/core/src/ops/try_trait.rs

+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
use crate::ops::ControlFlow;
2+
3+
/// The trait used for a variety of operations related to short-circuits,
4+
/// such as the `?` operator, `try {}` blocks, and `try_*` methods.
5+
///
6+
/// # Using `Try` in Generic Code
7+
///
8+
/// `Iterator::try_fold` was stabilized to call back in Rust 1.27, but
9+
/// this trait is much newer. To illustrate the various associated types and
10+
/// methods, let's implement our own version.
11+
///
12+
/// As a reminder, an infallible version of a fold looks something like this:
13+
/// ```
14+
/// fn simple_fold<A, T>(
15+
/// iter: impl Iterator<Item = T>,
16+
/// mut accum: A,
17+
/// mut f: impl FnMut(A, T) -> A,
18+
/// ) -> A {
19+
/// for x in iter {
20+
/// accum = f(accum, x);
21+
/// }
22+
/// accum
23+
/// }
24+
/// ```
25+
///
26+
/// So instead of `f` returning just an `A`, we'll need it to return some other
27+
/// type that produces an `A` in the "don't short circuit" path. Conveniently,
28+
/// that's also the type we need to return from the function.
29+
///
30+
/// Let's add a new generic parameter `R` for that type, and bound it to the
31+
/// output type that we want:
32+
/// ```
33+
/// # #![feature(try_trait_v2)]
34+
/// # #![feature(try_trait_transition)]
35+
/// # use std::ops::TryV2 as Try;
36+
/// fn simple_try_fold_1<A, T, R: Try<Output = A>>(
37+
/// iter: impl Iterator<Item = T>,
38+
/// mut accum: A,
39+
/// mut f: impl FnMut(A, T) -> R,
40+
/// ) -> R {
41+
/// todo!()
42+
/// }
43+
/// ```
44+
///
45+
/// `Try` is also the trait we need to get the updated accumulator from `f`'s return
46+
/// value and return the result if we manage to get through the entire iterator:
47+
/// ```
48+
/// # #![feature(try_trait_v2)]
49+
/// # #![feature(try_trait_transition)]
50+
/// # #![feature(control_flow_enum)]
51+
/// # use std::ops::{ControlFlow, TryV2 as Try};
52+
/// fn simple_try_fold_2<A, T, R: Try<Output = A>>(
53+
/// iter: impl Iterator<Item = T>,
54+
/// mut accum: A,
55+
/// mut f: impl FnMut(A, T) -> R,
56+
/// ) -> R {
57+
/// for x in iter {
58+
/// let cf = f(accum, x).branch();
59+
/// match cf {
60+
/// ControlFlow::Continue(a) => accum = a,
61+
/// ControlFlow::Break(_) => todo!(),
62+
/// }
63+
/// }
64+
/// R::from_output(accum)
65+
/// }
66+
/// ```
67+
///
68+
/// We'll also need `FromResidual::from_residual` to turn the residual back into
69+
/// the original type. But because it's a supertrait of `Try`, we don't need to
70+
/// mention it in the bounds. All types which implement `Try` can always be
71+
/// recreated from their corresponding residual, so we'll just call it:
72+
/// ```
73+
/// # #![feature(try_trait_v2)]
74+
/// # #![feature(try_trait_transition)]
75+
/// # #![feature(control_flow_enum)]
76+
/// # use std::ops::{ControlFlow, TryV2 as Try};
77+
/// pub fn simple_try_fold_3<A, T, R: Try<Output = A>>(
78+
/// iter: impl Iterator<Item = T>,
79+
/// mut accum: A,
80+
/// mut f: impl FnMut(A, T) -> R,
81+
/// ) -> R {
82+
/// for x in iter {
83+
/// let cf = f(accum, x).branch();
84+
/// match cf {
85+
/// ControlFlow::Continue(a) => accum = a,
86+
/// ControlFlow::Break(r) => return R::from_residual(r),
87+
/// }
88+
/// }
89+
/// R::from_output(accum)
90+
/// }
91+
/// ```
92+
///
93+
/// But this "call `branch`, then `match` on it, and `return` if it was a
94+
/// `Break`" is exactly what happens inside the `?` operator. So rather than
95+
/// do all this manually, we can just use `?` instead:
96+
/// ```compile_fail (enable again once ? converts to the new trait)
97+
/// # #![feature(try_trait_v2)]
98+
/// # #![feature(try_trait_transition)]
99+
/// # use std::ops::TryV2 as Try;
100+
/// fn simple_try_fold<A, T, R: Try<Output = A>>(
101+
/// iter: impl Iterator<Item = T>,
102+
/// mut accum: A,
103+
/// mut f: impl FnMut(A, T) -> R,
104+
/// ) -> R {
105+
/// for x in iter {
106+
/// accum = f(accum, x)?;
107+
/// }
108+
/// R::from_output(accum)
109+
/// }
110+
/// ```
111+
#[unstable(feature = "try_trait_v2", issue = "84277")]
112+
pub trait Try: FromResidual {
113+
/// The type of the value produced by `?` when *not* short-circuiting.
114+
#[unstable(feature = "try_trait_v2", issue = "84277")]
115+
type Output;
116+
117+
/// The type of the value passed to [`FromResidual::from_residual`]
118+
/// as part of `?` when short-circuiting.
119+
///
120+
/// This represents the possible values of the `Self` type which are *not*
121+
/// represented by the `Output` type.
122+
///
123+
/// # Note to Implementors
124+
///
125+
/// The choice of this type is critical to interconversion.
126+
/// Unlike the `Output` type, which will often be a raw generic type,
127+
/// this type is typically a newtype of some sort to "color" the type
128+
/// so that it's distinguishable from the residuals of other types.
129+
///
130+
/// This is why `Result<T, E>::Residual` is not `E`, but `Result<Infallible, E>`.
131+
/// That way it's distinct from `ControlFlow<E>::Residual`, for example,
132+
/// and thus `?` on `ControlFlow` cannot be used in a method returning `Result`.
133+
///
134+
/// In a type that's generic on a parameter that's used as the `Output` type,
135+
/// call it `Foo<T> : Try` where `Foo<T>::Output == T`, it's typically easiest
136+
/// to make the corresponding `Residual` type by filling in that generic
137+
/// with an uninhabited type: `type Residual = Foo<Infallible>;`.
138+
#[unstable(feature = "try_trait_v2", issue = "84277")]
139+
type Residual;
140+
141+
/// Wraps up a value such that `?` on the value will produce the original value.
142+
///
143+
/// # Examples
144+
///
145+
/// ```
146+
/// #![feature(try_trait_v2)]
147+
/// #![feature(control_flow_enum)]
148+
/// #![feature(try_trait_transition)]
149+
/// use std::ops::TryV2 as Try;
150+
///
151+
/// assert_eq!(<Result<_, String> as Try>::from_output(3), Ok(3));
152+
/// assert_eq!(<Option<_> as Try>::from_output(4), Some(4));
153+
/// assert_eq!(
154+
/// <std::ops::ControlFlow<String, _> as Try>::from_output(5),
155+
/// std::ops::ControlFlow::Continue(5),
156+
/// );
157+
///
158+
/// # fn make_question_mark_work() -> Option<()> {
159+
/// assert_eq!(Option::from_output(4)?, 4);
160+
/// # None }
161+
/// # make_question_mark_work();
162+
///
163+
/// // This is used, for example, on the accumulator in `try_fold`:
164+
/// let r = std::iter::empty().try_fold(4, |_, ()| -> Option<_> { unreachable!() });
165+
/// assert_eq!(r, Some(4));
166+
/// ```
167+
#[unstable(feature = "try_trait_v2", issue = "84277")]
168+
fn from_output(output: Self::Output) -> Self;
169+
170+
/// Used in `?` to decide whether the operator should produce a value
171+
/// (because this returned [`ControlFlow::Continue`])
172+
/// or propagate a value back to the caller
173+
/// (because this returned [`ControlFlow::Break`]).
174+
///
175+
/// # Examples
176+
///
177+
/// ```
178+
/// #![feature(try_trait_v2)]
179+
/// #![feature(control_flow_enum)]
180+
/// #![feature(try_trait_transition)]
181+
/// use std::ops::{ControlFlow, TryV2 as Try};
182+
///
183+
/// assert_eq!(Ok::<_, String>(3).branch(), ControlFlow::Continue(3));
184+
/// assert_eq!(Err::<String, _>(3).branch(), ControlFlow::Break(Err(3)));
185+
///
186+
/// assert_eq!(Some(3).branch(), ControlFlow::Continue(3));
187+
/// assert_eq!(None::<String>.branch(), ControlFlow::Break(None));
188+
///
189+
/// assert_eq!(ControlFlow::<String, _>::Continue(3).branch(), ControlFlow::Continue(3));
190+
/// assert_eq!(
191+
/// ControlFlow::<_, String>::Break(3).branch(),
192+
/// ControlFlow::Break(ControlFlow::Break(3)),
193+
/// );
194+
/// ```
195+
#[unstable(feature = "try_trait_v2", issue = "84277")]
196+
fn branch(self) -> ControlFlow<Self::Residual, Self::Output>;
197+
}
198+
199+
/// Used to specify which residuals can be converted into which [`Try`] types.
200+
///
201+
/// Every `Try` type needs to be recreatable from its own associated
202+
/// `Residual` type, but can also have additional `FromResidual` implementations
203+
/// to support interconversion with other `Try` types.
204+
#[unstable(feature = "try_trait_v2", issue = "84277")]
205+
pub trait FromResidual<R = <Self as Try>::Residual> {
206+
/// Produces the return value of the function from the residual
207+
/// when the `?` operator results in an early exit.
208+
///
209+
/// # Examples
210+
///
211+
/// ```
212+
/// #![feature(try_trait_v2)]
213+
/// #![feature(control_flow_enum)]
214+
/// use std::ops::{ControlFlow, FromResidual};
215+
///
216+
/// assert_eq!(Result::<String, i64>::from_residual(Err(3_u8)), Err(3));
217+
/// assert_eq!(Option::<String>::from_residual(None), None);
218+
/// assert_eq!(
219+
/// ControlFlow::<_, String>::from_residual(ControlFlow::Break(5)),
220+
/// ControlFlow::Break(5),
221+
/// );
222+
/// ```
223+
#[unstable(feature = "try_trait_v2", issue = "84277")]
224+
fn from_residual(residual: R) -> Self;
225+
}

library/core/src/option.rs

+31-2
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@
150150
use crate::iter::{FromIterator, FusedIterator, TrustedLen};
151151
use crate::pin::Pin;
152152
use crate::{
153-
hint, mem,
154-
ops::{self, Deref, DerefMut},
153+
convert, hint, mem,
154+
ops::{self, ControlFlow, Deref, DerefMut},
155155
};
156156

157157
/// The `Option` type. See [the module level documentation](self) for more.
@@ -1660,6 +1660,35 @@ impl<T> ops::Try for Option<T> {
16601660
}
16611661
}
16621662

1663+
#[unstable(feature = "try_trait_v2", issue = "84277")]
1664+
impl<T> ops::TryV2 for Option<T> {
1665+
type Output = T;
1666+
type Residual = Option<convert::Infallible>;
1667+
1668+
#[inline]
1669+
fn from_output(output: Self::Output) -> Self {
1670+
Some(output)
1671+
}
1672+
1673+
#[inline]
1674+
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
1675+
match self {
1676+
Some(v) => ControlFlow::Continue(v),
1677+
None => ControlFlow::Break(None),
1678+
}
1679+
}
1680+
}
1681+
1682+
#[unstable(feature = "try_trait_v2", issue = "84277")]
1683+
impl<T> ops::FromResidual for Option<T> {
1684+
#[inline]
1685+
fn from_residual(residual: Option<convert::Infallible>) -> Self {
1686+
match residual {
1687+
None => None,
1688+
}
1689+
}
1690+
}
1691+
16631692
impl<T> Option<Option<T>> {
16641693
/// Converts from `Option<Option<T>>` to `Option<T>`
16651694
///

library/core/src/result.rs

+30-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@
228228
#![stable(feature = "rust1", since = "1.0.0")]
229229

230230
use crate::iter::{self, FromIterator, FusedIterator, TrustedLen};
231-
use crate::ops::{self, Deref, DerefMut};
231+
use crate::ops::{self, ControlFlow, Deref, DerefMut};
232232
use crate::{convert, fmt, hint};
233233

234234
/// `Result` is a type that represents either success ([`Ok`]) or failure ([`Err`]).
@@ -1646,3 +1646,32 @@ impl<T, E> ops::Try for Result<T, E> {
16461646
Err(v)
16471647
}
16481648
}
1649+
1650+
#[unstable(feature = "try_trait_v2", issue = "84277")]
1651+
impl<T, E> ops::TryV2 for Result<T, E> {
1652+
type Output = T;
1653+
type Residual = Result<convert::Infallible, E>;
1654+
1655+
#[inline]
1656+
fn from_output(output: Self::Output) -> Self {
1657+
Ok(output)
1658+
}
1659+
1660+
#[inline]
1661+
fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
1662+
match self {
1663+
Ok(v) => ControlFlow::Continue(v),
1664+
Err(e) => ControlFlow::Break(Err(e)),
1665+
}
1666+
}
1667+
}
1668+
1669+
#[unstable(feature = "try_trait_v2", issue = "84277")]
1670+
impl<T, E, F: From<E>> ops::FromResidual<Result<convert::Infallible, E>> for Result<T, F> {
1671+
#[inline]
1672+
fn from_residual(residual: Result<convert::Infallible, E>) -> Self {
1673+
match residual {
1674+
Err(e) => Err(From::from(e)),
1675+
}
1676+
}
1677+
}

0 commit comments

Comments
 (0)