9 个不稳定版本 (3 个破坏性更改)
0.4.3 | 2023年7月18日 |
---|---|
0.4.2 | 2023年7月18日 |
0.4.1 | 2023年4月9日 |
0.3.1 | 2023年4月2日 |
0.1.1 | 2023年3月14日 |
#395 在 Rust 模式
每月下载量 31 次
在 3 个 软件包中使用 (2 个直接使用)
38KB
541 行
capture-it —
查看示例
有关详细文档,请参阅capture_it::capture
用法
使用类似于现代 C++ 的 lambda 捕获规则的语法创建闭包。`capture!` 宏的第一个参数是一个数组,列出闭包要捕获的参数,第二个参数指定一个 'async move' 块或 'move' 闭包。(为了更明确地指示使用了 'move' 闭包,如果任何异步或闭包函数缺少 'move' 标记,则会引发编译时错误)。
以下示例演示了如何通过使用 `index` 标识符捕获任意表达式(`0`)来创建生成器闭包。
use capture_it::capture;
// You can capture an expression, as we do in c++'s lambda capture.
//
// Any identifier prefixed with *(asterisk) declared as mutable.
let mut gen = capture!([*index = 0], move || {
index += 1;
index
});
assert!((gen(), gen(), gen(), gen(), gen()) == (1, 2, 3, 4, 5));
由于 `capture` 宏的函数参数必须使用 `move` 闭包,因此必须显式列出引用捕获;它们由 `&` 或 `&mut` 前缀表示,如常规 Rust 语法。
use capture_it::capture;
let mut num = 0;
let mut gen = capture!([&mut num], move || {
*num += 1;
*num
});
assert!((gen(), gen(), gen(), gen(), gen()) == (1, 2, 3, 4, 5));
`capture!` 宏默认为每个传入的参数调用 `Clone::clone`,这是创建闭包的一种更方便的方式。
use capture_it::capture;
use std::sync::{Arc, Mutex};
let arc = Arc::new(Mutex::new(0));
// From this ...
std::thread::spawn({
let arc = arc.clone();
move || {
*arc.lock().unwrap() += 1;
}
});
// To this
std::thread::spawn(capture!([arc], move || {
*arc.lock().unwrap() += 1;
}));
// The naive spin wait ...
while Arc::strong_count(&arc) > 1 {
std::thread::yield_now();
}
assert_eq!(*arc.lock().unwrap(), 2);
此宏特别适用于需要通过 `Clone` 将多个 `Arc` 实例传递到不同闭包的情况。查看以下示例,了解它是如何简化传统块捕获的。
use capture_it::capture;
use std::sync::Arc;
let arc = Arc::new(());
let arc2 = arc.clone(); // let's just think these are all different variables
let arc3 = arc.clone();
let arc4 = arc.clone();
let while_strong_count = |arc: &Arc<()>, pred_continue: fn(usize) -> bool| {
while pred_continue(Arc::strong_count(arc)) {
std::thread::yield_now();
}
};
// Before, when you have to capture variables by copy ...
std::thread::spawn({
let arc = arc.clone();
let arc2 = arc2.clone();
let arc3 = arc3.clone();
let arc4 = arc4.clone();
move || {
while_strong_count(&arc, |x| x >= 8);
// we have to explicitly capture them.
drop((arc2, arc3, arc4));
}
});
// Then, we can write same logic with above, but in much more concise way
std::thread::spawn(capture!([arc, arc2, arc3, arc4], move || {
while_strong_count(&arc, |x| x >= 12);
// `capture!` macro automatically captures all specified variables into closure,
// thus, we don't need to explicitly capture them.
// drop((arc2, arc3, arc4));
}));
assert!(Arc::strong_count(&arc) == 12);
// as all variables are captured by clone, we can still owning `arc*` series
drop((arc2, arc3, arc4));
while_strong_count(&arc, |x| x > 1);
除了捕获列表中指定的变量外,所有变量都遵循Rust的正常闭包规则,因此如果您需要获取变量的所有权,只需将其名称从捕获列表中删除即可。
use capture_it::capture;
use std::sync::Arc;
let cloned = Arc::new(());
let moved = cloned.clone();
std::thread::spawn(capture!([cloned], move || {
// Explicit 'move' capture
drop(moved);
}));
// 'moved' was moved. So we cannot use it here.
// drop(moved);
while Arc::strong_count(&cloned) > 1 {
std::thread::yield_now();
}
异步块遵循相同的规则。
use capture_it::capture;
use futures::{SinkExt, StreamExt};
let (tx, mut rx) = futures::channel::mpsc::unbounded::<usize>();
let task1 = capture!([*tx], async move {
// `move` is mandatory
for val in 1..=3 {
tx.send(val).await.unwrap();
}
});
let task2 = capture!([*tx], async move {
for val in 4..=6 {
tx.send(val).await.unwrap();
}
});
drop(tx); // we still have ownership of tx
task2.await;
task1.await;
for val in (4..=6).chain(1..=3) {
assert_eq!(rx.next().await.unwrap(), val);
}
额外说明:
capture
宏包含几个语法糖。例如,如果您想捕获类型 &str
作为相应的 ToOwned
类型,即 String
,您可以使用 Own(..)
装饰器。
use capture_it::capture;
let hello = "hello, world!";
let mut gen = capture!([*Own(hello), *times = 0], move || {
times += 1;
hello.push_str(×.to_string());
hello.clone()
});
assert_eq!(gen(), "hello, world!1");
assert_eq!(gen(), "hello, world!12");
assert_eq!(gen(), "hello, world!123");
Weak
装饰器用于捕获 Arc
或 Rc
的降级实例。
use capture_it::capture;
use std::rc::Rc;
use std::sync::Arc;
let rc = Rc::new(());
let arc = Arc::new(());
let closure = capture!([Weak(rc), Weak(arc)], move || {
assert!(rc.upgrade().is_none());
assert!(arc.upgrade().is_some());
});
drop(rc); // Let weak pointer upgrade of 'rc' fail
closure();
Some
装饰器对于在 FnMut
函数中模拟 FnOnce
很有用。
use capture_it::capture;
let initial_value = ();
let mut increment = 0;
let mut closure = capture!([*Some(initial_value), &mut increment], move || {
if let Some(_) = initial_value.take() {
// Evaluated only once, as we can take out `initial_value` only for single time...
*increment = 100;
} else {
*increment += 1;
}
});
closure();
closure();
closure();
assert_eq!(increment, 102);
任何具有单个参数的函数调用都可以用作装饰器。例如,capture
宏的正常克隆表示形式被替换为 Clone::clone(var)
。
use capture_it::capture;
let clone1 = std::sync::Arc::new(());
let clone2 = clone1.clone();
// following capture statement, `clone1` and `Clone::clone(&clone2)` behave equivalent.
let closure = capture!([clone1, Clone::clone(&clone2)], move || {
drop((clone1, clone2)); // Explicit drop will make this closure `FnOnce`
});
closure();
或者,您可以将调用 self
的函数的返回值作为其变量名来捕获。函数调用可以包含参数,但有一些限制;例如,重新链接到函数的返回值将不会工作(例如,var.foo().bar()....
)。只允许一个函数调用。
装饰器对于捕获简单的类型变化很有用;如果您想捕获复杂表达式,最好使用 a=b
的赋值语法。
use std::{rc::Rc, sync::Arc};
use capture_it::capture;
let arc = Arc::new(());
let rc = Rc::new(());
let weak_arc = Arc::downgrade(&arc);
let weak_rc = Rc::downgrade(&rc);
drop(arc);
// The return value of `.upgrade()` will be captured as of its name.
let closure = capture!([weak_arc.upgrade(), weak_rc.upgrade()], move || {
assert!(weak_arc.is_none());
assert!(weak_rc.is_some());
});
closure();
琐事:
其他闭包包使用更直观的捕获语法...
例如,closure
包中的闭包参数语法更直观。
另一方面,用于表示 *
的可变性的前缀是不直观且难以理解的 - 我们为什么要这样做呢?
引入新的语法是一个很有吸引力的选项,但默认情况下,大多数这类尝试都无法被 rustfmt
工具很好地理解。
由于闭包宏通常将函数体作为宏参数传递,如果 rustfmt
解析器无法解析宏参数,则较长的体将失去格式化器的优势。
另一方面,capture_it::capture
宏是Rust的有效语法(至少在语法上),它只是简单地作为宏参数传递一个数组和单个函数块。(同样,括在方括号中的捕获列表可以用类似C++的方式使用。)
因此,您在 capture_it::capture
宏中定义的任何捕获和功能块都可以由 rustfmt
格式化,这是我个人认为非常重要的一点。