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 orcontinue
’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 acceptcontinue
s in addition tobreak
s. extern-label
: the label thecbit!
body is allowed tobreak
orcontinue
out to.
- An optional
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);