Skip to content

Commit

Permalink
Expand the docs for ops::ControlFlow a bit
Browse files Browse the repository at this point in the history
Since I was writing some examples for an RFC anyway.
  • Loading branch information
scottmcm committed Feb 7, 2021
1 parent 08fdbd5 commit 1b7309e
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 3 deletions.
86 changes: 83 additions & 3 deletions library/core/src/ops/control_flow.rs
Original file line number Diff line number Diff line change
@@ -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<T> {
/// value: T,
/// left: Option<Box<TreeNode<T>>>,
/// right: Option<Box<TreeNode<T>>>,
/// }
///
/// impl<T> TreeNode<T> {
/// pub fn traverse_inorder<B>(&self, mut f: impl FnMut(&T) -> ControlFlow<B>) -> ControlFlow<B> {
/// 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<B, C = ()> {
/// 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<A, B>` <-> `Result<B, A>`
// is a no-op conversion in the `Try` implementation.
}

#[unstable(feature = "control_flow_enum", reason = "new API", issue = "75744")]
Expand All @@ -33,13 +83,33 @@ impl<B, C> Try for ControlFlow<B, C> {

impl<B, C> ControlFlow<B, C> {
/// Returns `true` if this is a `Break` variant.
///
/// # Examples
///
/// ```
/// #![feature(control_flow_enum)]
/// use std::ops::ControlFlow;
///
/// assert!(ControlFlow::<i32, String>::Break(3).is_break());
/// assert!(!ControlFlow::<String, i32>::Continue(3).is_break());
/// ```
#[inline]
#[unstable(feature = "control_flow_enum", reason = "new API", issue = "75744")]
pub fn is_break(&self) -> bool {
matches!(*self, ControlFlow::Break(_))
}

/// Returns `true` if this is a `Continue` variant.
///
/// # Examples
///
/// ```
/// #![feature(control_flow_enum)]
/// use std::ops::ControlFlow;
///
/// assert!(!ControlFlow::<i32, String>::Break(3).is_continue());
/// assert!(ControlFlow::<String, i32>::Continue(3).is_continue());
/// ```
#[inline]
#[unstable(feature = "control_flow_enum", reason = "new API", issue = "75744")]
pub fn is_continue(&self) -> bool {
Expand All @@ -48,6 +118,16 @@ impl<B, C> ControlFlow<B, C> {

/// 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::<i32, String>::Break(3).break_value(), Some(3));
/// assert_eq!(ControlFlow::<String, i32>::Continue(3).break_value(), None);
/// ```
#[inline]
#[unstable(feature = "control_flow_enum", reason = "new API", issue = "75744")]
pub fn break_value(self) -> Option<B> {
Expand Down
1 change: 1 addition & 0 deletions library/core/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
2 changes: 2 additions & 0 deletions library/core/tests/ops.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod control_flow;

use core::ops::{Bound, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive};
use core::ops::{Deref, DerefMut};

Expand Down
18 changes: 18 additions & 0 deletions library/core/tests/ops/control_flow.rs
Original file line number Diff line number Diff line change
@@ -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::<i32, i32>::Break(3)),
discriminant_value(&Result::<i32, i32>::Err(3)),
);
assert_eq!(
discriminant_value(&ControlFlow::<i32, i32>::Continue(3)),
discriminant_value(&Result::<i32, i32>::Ok(3)),
);
}

0 comments on commit 1b7309e

Please sign in to comment.