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