Rollup merge of #81849 - scottmcm:control-flow-comments, r=Mark-Simulacrum

Expand the docs for ops::ControlFlow a bit

Since I was writing some examples for an RFC anyway.

And I almost made the mistake of reordering the variants, so added a note and a test about that.
This commit is contained in:
Dylan DPC 2021-02-09 02:39:59 +01:00 committed by GitHub
commit a63085dc5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 104 additions and 3 deletions

View File

@ -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")]
@ -33,6 +83,16 @@ 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 {
@ -40,6 +100,16 @@ impl<B, C> ControlFlow<B, C> {
}
/// 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 {
@ -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> {

View File

@ -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)]

View File

@ -1,3 +1,5 @@
mod control_flow;
use core::ops::{Bound, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive};
use core::ops::{Deref, DerefMut};

View File

@ -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)),
);
}