#iterator #proc-macro #closures #control-flow #break #syntax #regular

cbit

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

1个不稳定版本

0.1.0 2023年12月1日

628 in 过程宏

MIT 许可证

32KB
323

Cbit

Cbit是一个过程宏,用于使用回调式迭代器和for循环语法和功能。

概述

cbit(代表 closure-based iterator,基于闭包的迭代器)是一个crate,它允许你使用在for循环中使用像常规Rust Iterator一样的迭代器函数,这些函数通过闭包来处理每个元素。要创建一个迭代器,只需定义一个函数,该函数接受一个闭包作为最后一个参数。函数和闭包都必须返回一个包含一些泛型Break类型的ControlFlow对象。

use std::ops::ControlFlow;

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

从那里,你可以使用迭代器像常规for循环一样,通过使用cbit!宏来驱动它。

fn demo(n: u64) -> u64 {
    let mut c = 0;
    cbit::cbit!(for i in up_to(n) {
        c += i;
    });
    c
}

尽管for循环的体实际上嵌套在一个闭包中,但它支持所有预期的常规控制流机制

你可以提前return到外部函数...

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);

你可以在体中使用breakcontinue...

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);

你甚至可以breakcontinue到体外的作用域!

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.

有关更多关于其语法和特定行为的详细信息,请参阅cbit!的文档。

优势和缺点

基于闭包的迭代器与Rust优化器的配合比协程及其稳定用户空间对应物(截至rustc 1.74.0)要好的多。

这是常规循环实现阶乘的汇编...

pub fn regular(n: u64) -> u64 {
    let mut c = 0;
    for i in 0..n {
        c += i;
    }
    c
}
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

...这里是用cbit重新实现循环的汇编

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<B>(n: u64, mut f: impl FnMut(u64) -> ControlFlow<B>) -> ControlFlow<B> {
    for i in 0..n {
        f(i)?;
    }
    ControlFlow::Continue(())
}
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

除了标签名称外,它们完全相同!

与此同时,使用 rustc 1.76.0-nightly (49b3924bd 2023-11-27) 的协程编写的相同示例产生了更差的代码生成(永久链接

#![feature(coroutines, coroutine_trait, iter_from_coroutine)]

use std::{iter::from_coroutine, ops::Coroutine};

fn upto_n(n: u64) -> impl Coroutine<Yield = u64, Return = ()> {
    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
}
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

与这个功能类似,用户空间的实现,如 genawaiter,也可以看到类似的情况。

然而,更通用的协程实现为了换取潜在的性能下降,提供了巨大的表达能力。从根本上讲,cbit 迭代器不能交织在一起,使得如 zip 这样的适配器无法实现——而协程则没有这个问题。

依赖项

~275–720KB
~17K SLoC