From 1b7309edd6b0e0a50b1e65c7888e3aa888baa10b Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Sat, 6 Feb 2021 22:36:05 -0800 Subject: [PATCH] Expand the docs for ops::ControlFlow a bit Since I was writing some examples for an RFC anyway. --- library/core/src/ops/control_flow.rs | 86 +++++++++++++++++++++++++- library/core/tests/lib.rs | 1 + library/core/tests/ops.rs | 2 + library/core/tests/ops/control_flow.rs | 18 ++++++ 4 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 library/core/tests/ops/control_flow.rs diff --git a/library/core/src/ops/control_flow.rs b/library/core/src/ops/control_flow.rs index 4834ca74b8270..2f78ba8f28e29 100644 --- a/library/core/src/ops/control_flow.rs +++ b/library/core/src/ops/control_flow.rs @@ -1,13 +1,63 @@ use crate::ops::Try; -/// Used to make try_fold closures more like normal loops +/// Used to tell an operation whether it should exit early or go on as usual. +/// +/// This is used when exposing things (like graph traversals or visitors) where +/// you want the user to be able to choose whether to exit early. +/// Having the enum makes it clearer -- no more wondering "wait, what did `false` +/// mean again?" -- and allows including a value. +/// +/// # Examples +/// +/// Early-exiting from [`Iterator::try_for_each`]: +/// ``` +/// #![feature(control_flow_enum)] +/// use std::ops::ControlFlow; +/// +/// let r = (2..100).try_for_each(|x| { +/// if 403 % x == 0 { +/// return ControlFlow::Break(x) +/// } +/// +/// ControlFlow::Continue(()) +/// }); +/// assert_eq!(r, ControlFlow::Break(13)); +/// ``` +/// +/// A basic tree traversal: +/// ```no_run +/// #![feature(control_flow_enum)] +/// use std::ops::ControlFlow; +/// +/// pub struct TreeNode { +/// value: T, +/// left: Option>>, +/// right: Option>>, +/// } +/// +/// impl TreeNode { +/// pub fn traverse_inorder(&self, mut f: impl FnMut(&T) -> ControlFlow) -> ControlFlow { +/// if let Some(left) = &self.left { +/// left.traverse_inorder(&mut f)?; +/// } +/// f(&self.value)?; +/// if let Some(right) = &self.right { +/// right.traverse_inorder(&mut f)?; +/// } +/// ControlFlow::Continue(()) +/// } +/// } +/// ``` #[unstable(feature = "control_flow_enum", reason = "new API", issue = "75744")] #[derive(Debug, Clone, Copy, PartialEq)] pub enum ControlFlow { - /// Continue in the loop, using the given value for the next iteration + /// Move on to the next phase of the operation as normal. Continue(C), - /// Exit the loop, yielding the given value + /// Exit the operation without running subsequent phases. Break(B), + // Yes, the order of the variants doesn't match the type parameters. + // They're in this order so that `ControlFlow` <-> `Result` + // is a no-op conversion in the `Try` implementation. } #[unstable(feature = "control_flow_enum", reason = "new API", issue = "75744")] @@ -33,6 +83,16 @@ impl Try for ControlFlow { impl ControlFlow { /// Returns `true` if this is a `Break` variant. + /// + /// # Examples + /// + /// ``` + /// #![feature(control_flow_enum)] + /// use std::ops::ControlFlow; + /// + /// assert!(ControlFlow::::Break(3).is_break()); + /// assert!(!ControlFlow::::Continue(3).is_break()); + /// ``` #[inline] #[unstable(feature = "control_flow_enum", reason = "new API", issue = "75744")] pub fn is_break(&self) -> bool { @@ -40,6 +100,16 @@ impl ControlFlow { } /// Returns `true` if this is a `Continue` variant. + /// + /// # Examples + /// + /// ``` + /// #![feature(control_flow_enum)] + /// use std::ops::ControlFlow; + /// + /// assert!(!ControlFlow::::Break(3).is_continue()); + /// assert!(ControlFlow::::Continue(3).is_continue()); + /// ``` #[inline] #[unstable(feature = "control_flow_enum", reason = "new API", issue = "75744")] pub fn is_continue(&self) -> bool { @@ -48,6 +118,16 @@ impl ControlFlow { /// Converts the `ControlFlow` into an `Option` which is `Some` if the /// `ControlFlow` was `Break` and `None` otherwise. + /// + /// # Examples + /// + /// ``` + /// #![feature(control_flow_enum)] + /// use std::ops::ControlFlow; + /// + /// assert_eq!(ControlFlow::::Break(3).break_value(), Some(3)); + /// assert_eq!(ControlFlow::::Continue(3).break_value(), None); + /// ``` #[inline] #[unstable(feature = "control_flow_enum", reason = "new API", issue = "75744")] pub fn break_value(self) -> Option { diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index 4dc86e0f5f40f..339691b117694 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -15,6 +15,7 @@ #![feature(const_maybe_uninit_assume_init)] #![feature(const_ptr_read)] #![feature(const_ptr_offset)] +#![feature(control_flow_enum)] #![feature(core_intrinsics)] #![feature(core_private_bignum)] #![feature(core_private_diy_float)] diff --git a/library/core/tests/ops.rs b/library/core/tests/ops.rs index 53e5539fad91c..aa79dbac8f39d 100644 --- a/library/core/tests/ops.rs +++ b/library/core/tests/ops.rs @@ -1,3 +1,5 @@ +mod control_flow; + use core::ops::{Bound, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; use core::ops::{Deref, DerefMut}; diff --git a/library/core/tests/ops/control_flow.rs b/library/core/tests/ops/control_flow.rs new file mode 100644 index 0000000000000..eacfd63a6c48f --- /dev/null +++ b/library/core/tests/ops/control_flow.rs @@ -0,0 +1,18 @@ +use core::intrinsics::discriminant_value; +use core::ops::ControlFlow; + +#[test] +fn control_flow_discriminants_match_result() { + // This isn't stable surface area, but helps keep `?` cheap between them, + // even if LLVM can't always take advantage of it right now. + // (Sadly Result and Option are inconsistent, so ControlFlow can't match both.) + + assert_eq!( + discriminant_value(&ControlFlow::::Break(3)), + discriminant_value(&Result::::Err(3)), + ); + assert_eq!( + discriminant_value(&ControlFlow::::Continue(3)), + discriminant_value(&Result::::Ok(3)), + ); +}