]> git.lizzy.rs Git - rust.git/blob - library/core/src/ops/control_flow.rs
Auto merge of #105701 - RedDocMD:bug-105634, r=cjgillot
[rust.git] / library / core / src / ops / control_flow.rs
1 use crate::{convert, ops};
2
3 /// Used to tell an operation whether it should exit early or go on as usual.
4 ///
5 /// This is used when exposing things (like graph traversals or visitors) where
6 /// you want the user to be able to choose whether to exit early.
7 /// Having the enum makes it clearer -- no more wondering "wait, what did `false`
8 /// mean again?" -- and allows including a value.
9 ///
10 /// Similar to [`Option`] and [`Result`], this enum can be used with the `?` operator
11 /// to return immediately if the [`Break`] variant is present or otherwise continue normally
12 /// with the value inside the [`Continue`] variant.
13 ///
14 /// # Examples
15 ///
16 /// Early-exiting from [`Iterator::try_for_each`]:
17 /// ```
18 /// use std::ops::ControlFlow;
19 ///
20 /// let r = (2..100).try_for_each(|x| {
21 ///     if 403 % x == 0 {
22 ///         return ControlFlow::Break(x)
23 ///     }
24 ///
25 ///     ControlFlow::Continue(())
26 /// });
27 /// assert_eq!(r, ControlFlow::Break(13));
28 /// ```
29 ///
30 /// A basic tree traversal:
31 /// ```
32 /// use std::ops::ControlFlow;
33 ///
34 /// pub struct TreeNode<T> {
35 ///     value: T,
36 ///     left: Option<Box<TreeNode<T>>>,
37 ///     right: Option<Box<TreeNode<T>>>,
38 /// }
39 ///
40 /// impl<T> TreeNode<T> {
41 ///     pub fn traverse_inorder<B>(&self, f: &mut impl FnMut(&T) -> ControlFlow<B>) -> ControlFlow<B> {
42 ///         if let Some(left) = &self.left {
43 ///             left.traverse_inorder(f)?;
44 ///         }
45 ///         f(&self.value)?;
46 ///         if let Some(right) = &self.right {
47 ///             right.traverse_inorder(f)?;
48 ///         }
49 ///         ControlFlow::Continue(())
50 ///     }
51 ///     fn leaf(value: T) -> Option<Box<TreeNode<T>>> {
52 ///         Some(Box::new(Self { value, left: None, right: None }))
53 ///     }
54 /// }
55 ///
56 /// let node = TreeNode {
57 ///     value: 0,
58 ///     left: TreeNode::leaf(1),
59 ///     right: Some(Box::new(TreeNode {
60 ///         value: -1,
61 ///         left: TreeNode::leaf(5),
62 ///         right: TreeNode::leaf(2),
63 ///     }))
64 /// };
65 /// let mut sum = 0;
66 ///
67 /// let res = node.traverse_inorder(&mut |val| {
68 ///     if *val < 0 {
69 ///         ControlFlow::Break(*val)
70 ///     } else {
71 ///         sum += *val;
72 ///         ControlFlow::Continue(())
73 ///     }
74 /// });
75 /// assert_eq!(res, ControlFlow::Break(-1));
76 /// assert_eq!(sum, 6);
77 /// ```
78 ///
79 /// [`Break`]: ControlFlow::Break
80 /// [`Continue`]: ControlFlow::Continue
81 #[stable(feature = "control_flow_enum_type", since = "1.55.0")]
82 // ControlFlow should not implement PartialOrd or Ord, per RFC 3058:
83 // https://rust-lang.github.io/rfcs/3058-try-trait-v2.html#traits-for-controlflow
84 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
85 pub enum ControlFlow<B, C = ()> {
86     /// Move on to the next phase of the operation as normal.
87     #[stable(feature = "control_flow_enum_type", since = "1.55.0")]
88     #[lang = "Continue"]
89     Continue(C),
90     /// Exit the operation without running subsequent phases.
91     #[stable(feature = "control_flow_enum_type", since = "1.55.0")]
92     #[lang = "Break"]
93     Break(B),
94     // Yes, the order of the variants doesn't match the type parameters.
95     // They're in this order so that `ControlFlow<A, B>` <-> `Result<B, A>`
96     // is a no-op conversion in the `Try` implementation.
97 }
98
99 #[unstable(feature = "try_trait_v2", issue = "84277")]
100 #[rustc_const_unstable(feature = "const_convert", issue = "88674")]
101 impl<B, C> const ops::Try for ControlFlow<B, C> {
102     type Output = C;
103     type Residual = ControlFlow<B, convert::Infallible>;
104
105     #[inline]
106     fn from_output(output: Self::Output) -> Self {
107         ControlFlow::Continue(output)
108     }
109
110     #[inline]
111     fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
112         match self {
113             ControlFlow::Continue(c) => ControlFlow::Continue(c),
114             ControlFlow::Break(b) => ControlFlow::Break(ControlFlow::Break(b)),
115         }
116     }
117 }
118
119 #[unstable(feature = "try_trait_v2", issue = "84277")]
120 #[rustc_const_unstable(feature = "const_convert", issue = "88674")]
121 impl<B, C> const ops::FromResidual for ControlFlow<B, C> {
122     #[inline]
123     fn from_residual(residual: ControlFlow<B, convert::Infallible>) -> Self {
124         match residual {
125             ControlFlow::Break(b) => ControlFlow::Break(b),
126         }
127     }
128 }
129
130 #[unstable(feature = "try_trait_v2_residual", issue = "91285")]
131 #[rustc_const_unstable(feature = "const_try", issue = "74935")]
132 impl<B, C> const ops::Residual<C> for ControlFlow<B, convert::Infallible> {
133     type TryType = ControlFlow<B, C>;
134 }
135
136 impl<B, C> ControlFlow<B, C> {
137     /// Returns `true` if this is a `Break` variant.
138     ///
139     /// # Examples
140     ///
141     /// ```
142     /// use std::ops::ControlFlow;
143     ///
144     /// assert!(ControlFlow::<i32, String>::Break(3).is_break());
145     /// assert!(!ControlFlow::<String, i32>::Continue(3).is_break());
146     /// ```
147     #[inline]
148     #[stable(feature = "control_flow_enum_is", since = "1.59.0")]
149     pub fn is_break(&self) -> bool {
150         matches!(*self, ControlFlow::Break(_))
151     }
152
153     /// Returns `true` if this is a `Continue` variant.
154     ///
155     /// # Examples
156     ///
157     /// ```
158     /// use std::ops::ControlFlow;
159     ///
160     /// assert!(!ControlFlow::<i32, String>::Break(3).is_continue());
161     /// assert!(ControlFlow::<String, i32>::Continue(3).is_continue());
162     /// ```
163     #[inline]
164     #[stable(feature = "control_flow_enum_is", since = "1.59.0")]
165     pub fn is_continue(&self) -> bool {
166         matches!(*self, ControlFlow::Continue(_))
167     }
168
169     /// Converts the `ControlFlow` into an `Option` which is `Some` if the
170     /// `ControlFlow` was `Break` and `None` otherwise.
171     ///
172     /// # Examples
173     ///
174     /// ```
175     /// #![feature(control_flow_enum)]
176     /// use std::ops::ControlFlow;
177     ///
178     /// assert_eq!(ControlFlow::<i32, String>::Break(3).break_value(), Some(3));
179     /// assert_eq!(ControlFlow::<String, i32>::Continue(3).break_value(), None);
180     /// ```
181     #[inline]
182     #[unstable(feature = "control_flow_enum", reason = "new API", issue = "75744")]
183     pub fn break_value(self) -> Option<B> {
184         match self {
185             ControlFlow::Continue(..) => None,
186             ControlFlow::Break(x) => Some(x),
187         }
188     }
189
190     /// Maps `ControlFlow<B, C>` to `ControlFlow<T, C>` by applying a function
191     /// to the break value in case it exists.
192     #[inline]
193     #[unstable(feature = "control_flow_enum", reason = "new API", issue = "75744")]
194     pub fn map_break<T, F>(self, f: F) -> ControlFlow<T, C>
195     where
196         F: FnOnce(B) -> T,
197     {
198         match self {
199             ControlFlow::Continue(x) => ControlFlow::Continue(x),
200             ControlFlow::Break(x) => ControlFlow::Break(f(x)),
201         }
202     }
203
204     /// Converts the `ControlFlow` into an `Option` which is `Some` if the
205     /// `ControlFlow` was `Continue` and `None` otherwise.
206     ///
207     /// # Examples
208     ///
209     /// ```
210     /// #![feature(control_flow_enum)]
211     /// use std::ops::ControlFlow;
212     ///
213     /// assert_eq!(ControlFlow::<i32, String>::Break(3).continue_value(), None);
214     /// assert_eq!(ControlFlow::<String, i32>::Continue(3).continue_value(), Some(3));
215     /// ```
216     #[inline]
217     #[unstable(feature = "control_flow_enum", reason = "new API", issue = "75744")]
218     pub fn continue_value(self) -> Option<C> {
219         match self {
220             ControlFlow::Continue(x) => Some(x),
221             ControlFlow::Break(..) => None,
222         }
223     }
224
225     /// Maps `ControlFlow<B, C>` to `ControlFlow<B, T>` by applying a function
226     /// to the continue value in case it exists.
227     #[inline]
228     #[unstable(feature = "control_flow_enum", reason = "new API", issue = "75744")]
229     pub fn map_continue<T, F>(self, f: F) -> ControlFlow<B, T>
230     where
231         F: FnOnce(C) -> T,
232     {
233         match self {
234             ControlFlow::Continue(x) => ControlFlow::Continue(f(x)),
235             ControlFlow::Break(x) => ControlFlow::Break(x),
236         }
237     }
238 }
239
240 /// These are used only as part of implementing the iterator adapters.
241 /// They have mediocre names and non-obvious semantics, so aren't
242 /// currently on a path to potential stabilization.
243 impl<R: ops::Try> ControlFlow<R, R::Output> {
244     /// Create a `ControlFlow` from any type implementing `Try`.
245     #[inline]
246     pub(crate) fn from_try(r: R) -> Self {
247         match R::branch(r) {
248             ControlFlow::Continue(v) => ControlFlow::Continue(v),
249             ControlFlow::Break(v) => ControlFlow::Break(R::from_residual(v)),
250         }
251     }
252
253     /// Convert a `ControlFlow` into any type implementing `Try`;
254     #[inline]
255     pub(crate) fn into_try(self) -> R {
256         match self {
257             ControlFlow::Continue(v) => R::from_output(v),
258             ControlFlow::Break(v) => v,
259         }
260     }
261 }
262
263 impl<B> ControlFlow<B, ()> {
264     /// It's frequently the case that there's no value needed with `Continue`,
265     /// so this provides a way to avoid typing `(())`, if you prefer it.
266     ///
267     /// # Examples
268     ///
269     /// ```
270     /// #![feature(control_flow_enum)]
271     /// use std::ops::ControlFlow;
272     ///
273     /// let mut partial_sum = 0;
274     /// let last_used = (1..10).chain(20..25).try_for_each(|x| {
275     ///     partial_sum += x;
276     ///     if partial_sum > 100 { ControlFlow::Break(x) }
277     ///     else { ControlFlow::CONTINUE }
278     /// });
279     /// assert_eq!(last_used.break_value(), Some(22));
280     /// ```
281     #[unstable(feature = "control_flow_enum", reason = "new API", issue = "75744")]
282     pub const CONTINUE: Self = ControlFlow::Continue(());
283 }
284
285 impl<C> ControlFlow<(), C> {
286     /// APIs like `try_for_each` don't need values with `Break`,
287     /// so this provides a way to avoid typing `(())`, if you prefer it.
288     ///
289     /// # Examples
290     ///
291     /// ```
292     /// #![feature(control_flow_enum)]
293     /// use std::ops::ControlFlow;
294     ///
295     /// let mut partial_sum = 0;
296     /// (1..10).chain(20..25).try_for_each(|x| {
297     ///     if partial_sum > 100 { ControlFlow::BREAK }
298     ///     else { partial_sum += x; ControlFlow::CONTINUE }
299     /// });
300     /// assert_eq!(partial_sum, 108);
301     /// ```
302     #[unstable(feature = "control_flow_enum", reason = "new API", issue = "75744")]
303     pub const BREAK: Self = ControlFlow::Break(());
304 }