# Cbit A proc-macro to use callback-based iterators with `for`-loop syntax and functionality. ### Overview `cbit` (short for **c**losure-**b**ased **it**erator) is a crate which allows you to use iterator functions which call into a closure to process each element as if they were just a regular Rust `Iterator` in a `for` loop. To create an iterator, just define a function which takes in a closure as its last argument. Both the function and the closure must return a `ControlFlow` object with some generic `Break` type. ```rust use std::ops::ControlFlow; fn up_to(n: u64, mut f: impl FnMut(u64) -> ControlFlow) -> ControlFlow { for i in 0..n { f(i)?; } ControlFlow::Continue(()) } ``` From there, you can use the iterator like a regular `for`-loop by driving it using the `cbit!` macro. ```rust fn demo(n: u64) -> u64 { let mut c = 0; cbit::cbit!(for i in up_to(n) { c += i; }); c } ``` Although the body of the `for` loop is technically nested in a closure, it supports all the regular control-flow mechanisms one would expect: You can early-`return` to the outer function... ```rust fn demo(n: u64) -> u64 { let mut c = 0; cbit::cbit!(for i in up_to(n) { c += i; if c > 1000 { return u64::MAX; } }); c } assert_eq!(demo(500), u64::MAX); ``` You can `break` and `continue` in the body... ```rust fn demo(n: u64) -> u64 { let mut c = 0; cbit::cbit!('me: for i in up_to(n) { if i == 2 { continue 'me; // This label is optional. } c += i; if c > 5 { break; } }); c } assert_eq!(demo(5), 1 + 3 + 4); ``` And you can even `break` and `continue` to scopes outside the body! ```rust fn demo(n: u64) -> u64 { let mut c = 0; 'outer_1: loop { let something = 'outer_2: { cbit::cbit!(for i in up_to(n) break loop 'outer_1, 'outer_2 { if i == 5 && c < 20 { continue 'outer_1; } if i == 8 { break 'outer_2 c < 10; } c += i; }); false }; if something { assert!(c < 10); } else { break; } } c } demo(10); // I'm honestly not really sure what this function is supposed to do. ``` Check the documentation of [`cbit!`] for more details on its syntax and specific behavior. ### Advantages and Drawbacks Closure-based iterators play much nicer with the Rust optimizer than coroutines and their [stable `async` userland counterpart](https://docs.rs/genawaiter/latest/genawaiter/) do as of `rustc 1.74.0`. Here is the disassembly of a regular loop implementation of factorial: ```rust pub fn regular(n: u64) -> u64 { let mut c = 0; for i in 0..n { c += i; } c } ``` ```text asm::regular: Lfunc_begin7: push rbp mov rbp, rsp test rdi, rdi je LBB7_1 lea rax, [rdi - 1] lea rcx, [rdi - 2] mul rcx shld rdx, rax, 63 lea rax, [rdi + rdx - 1] pop rbp ret LBB7_1: xor eax, eax pop rbp ret ``` ...and here is the disassembly of the loop reimplemented in cbit: ```rust use std::ops::ControlFlow; pub fn cbit(n: u64) -> u64 { let mut c = 0; cbit::cbit!(for i in up_to(n) { c += i; }); c } fn up_to(n: u64, mut f: impl FnMut(u64) -> ControlFlow) -> ControlFlow { for i in 0..n { f(i)?; } ControlFlow::Continue(()) } ``` ```text asm::cbit: Lfunc_begin8: push rbp mov rbp, rsp test rdi, rdi je LBB8_1 lea rax, [rdi - 1] lea rcx, [rdi - 2] mul rcx shld rdx, rax, 63 lea rax, [rdi + rdx - 1] pop rbp ret LBB8_1: xor eax, eax pop rbp ret ``` Except for the label names, they're entirely identical! Meanwhile, the same example written with `rustc 1.76.0-nightly (49b3924bd 2023-11-27)`'s coroutines yields far worse codegen ([permalink](https://godbolt.org/z/Kjh9q195s)): ```no_compile #![feature(coroutines, coroutine_trait, iter_from_coroutine)] use std::{iter::from_coroutine, ops::Coroutine}; fn upto_n(n: u64) -> impl Coroutine { move || { for i in 0..n { yield i; } } } pub fn sum(n: u64) -> u64 { let mut c = 0; let mut co = std::pin::pin!(upto_n(n)); for i in from_coroutine(co) { c += i; } c } ``` ```text example::sum: xor edx, edx xor eax, eax test edx, edx je .LBB0_4 .LBB0_2: cmp edx, 3 jne .LBB0_3 cmp rcx, rdi jb .LBB0_7 jmp .LBB0_6 .LBB0_4: xor ecx, ecx cmp rcx, rdi jae .LBB0_6 .LBB0_7: setb dl movzx edx, dl add rax, rcx add rcx, rdx lea edx, [2*rdx + 1] test edx, edx jne .LBB0_2 jmp .LBB0_4 .LBB0_6: ret .LBB0_3: push rax lea rdi, [rip + str.0] lea rdx, [rip + .L__unnamed_1] mov esi, 34 call qword ptr [rip + core::panicking::panic@GOTPCREL] ud2 ``` A similar thing can be seen with userland implementations of this feature such as [`genawaiter`](https://docs.rs/genawaiter/latest/genawaiter/index.html). However, what more general coroutine implementations provide in exchange for potential performance degradation is immense expressivity. Fundamentally, `cbit` iterators cannot be interwoven, making adapters such as `zip` impossible to implement—something coroutines have no problem doing.