1个不稳定版本
0.1.0 | 2023年12月1日 |
---|
628 in 过程宏
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);
你可以在体中使用break
和continue
...
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);
你甚至可以break
和continue
到体外的作用域!
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