Macro cbit::cbit

source ·
cbit!() { /* proc-macro */ }
Expand description

A proc-macro to use callback-based iterators with for-loop syntax and functionality.

Syntax

('<loop-label: lifetime>:)? for <binding: pattern> in <iterator: function-call-expr>
    (break ((loop)? '<extern-label: lifetime>)*)?
{
    <body: token stream>
}

Arguments:

  • loop-label: This is the optional label used by your virtual loop. break’ing or continue’ing to this label will break out of and continue the cbit iterator respectively.
  • binding: This is the irrefutable pattern the iterator’s arguments will be decomposed into.
  • iterator: Syntactically, this can be any (potentially generic) function or method call expression and generics can be explicitly supplied if desired. See the iteration protocol section for details on the semantic requirements for this function.
  • The loop also contains an optional list of external control-flow labels which is started by the break keyword and is followed by a non-empty non-trailing comma-separated list of…
    • An optional loop keyword which, if specified, asserts that the label can accept continues in addition to breaks.
    • extern-label: the label the cbit! body is allowed to break or continue out to.

Iteration Protocol

The called function or method can take on any non-zero number of arguments but must accept a single-argument function closure as its last argument. The closure must be able to return a ControlFlow object with a generic Break type and the function must return a ControlFlow object with the same Break type.

use std::{iter::IntoIterator, ops::ControlFlow};

// A simple example...
fn up_to<B>(n: u64, mut f: impl FnMut(u64) -> ControlFlow<B>) -> ControlFlow<B> {
    for i in 0..n {
        f(i)?;
    }
    ControlFlow::Continue(())
}

// A slightly more involved example...
fn enumerate<I: IntoIterator, B>(
    values: I,
    index_offset: usize,
    mut f: impl FnMut((usize, I::Item),
) -> ControlFlow<B>) -> ControlFlow<B> {
    for (i, v) in values.into_iter().enumerate() {
        f((i + index_offset, v))?;
    }
    ControlFlow::Continue(())
}

The Continue parameter of the ControlFlow objects, meanwhile, is a lot more flexible. The Continue parameter on the return type of the inner closure designates the type users are expected to give back to the calling iterator function. Since users can run continue in the body, this type must implement Default.

The Continue parameter on the return type of the iterator function, meanwhile, can be used to return values from the cbit! macro expression. If users break out of loops with a non-unit output Continue type, they must provide this value themself.

use std::ops::ControlFlow;

fn demo(list: &[i32]) -> i32 {
    cbit::cbit!(for (accum, value) in reduce(0, list) {
        if *value > 100 {
            break -1;
        }
        accum + value
    })
}

fn reduce<T, I: IntoIterator, B>(
    initial: T,
    values: I,
    mut f: impl FnMut((T, I::Item)) -> ControlFlow<B, T>,
) -> ControlFlow<B, T> {
    let mut accum = initial;
    for value in values {
        accum = f((accum, value))?;
    }
    ControlFlow::Continue(accum)
}

assert_eq!(demo(&[1, 2, 3]), 6);
assert_eq!(demo(&[1, 2, 3, 4, 101, 8]), -1);