1 个不稳定版本
| 0.1.0 | 2021年11月26日 |
|---|
#20 在 #directive
每月 27 下载
26KB
417 行
更强大的闭包捕获
此包提供了简单的宏,让您能够表达更强大的闭包捕获。例如,您可以捕获值的克隆
use std::rc::Rc;
let my_val = Rc::new(1);
captures::capture!(clone my_val, move || {
// `my_val` is cloned here!
});
您还可以捕获任意表达式并覆盖 Edition-2021 捕获语义。最好的是,您甚至可以指定您的闭包不应该捕获您列出的任何变量之外的变量
let a = 1;
let b = 2;
captures::capture_only!(clone a, move || {
a + b // errors: `b` is unknown
})
有关详细信息,请参阅 完整文档。
lib.rs:
提供两个宏以实现更强大的闭包捕获。
背景
Rust 中的闭包功能强大,但并没有太多选项来修改它们捕获输出的方式。一个痛点往往是需要为闭包捕获 .clone() 一个 Arc<T> 或 Rc<T>。这种模式无法编译
fn needs_static<T: FnOnce() -> i32 + 'static>(f: T) -> i32 {
f()
}
let local: Rc<i32> = Rc::new(1);
// Try and capture a clone of the `Rc`
let mut f = || {
let in_closure = local.clone();
*in_closure.as_ref()
};
// `f` is not `'static`!
assert_eq!(needs_static(f), 1);
// `local` has not been captured!
assert_eq!(*local.as_ref(), 1);
这是因为当在闭包体中编写 local.clone() 时,这个 clone 调用不会执行直到闭包被调用;这意味着闭包实际上捕获了一个 &local,因此它不是 'static!将 f 设为 move 闭包并不能解决这个问题,因为在这种情况下,local 将被按值捕获,后面的 local.as_ref() 语句将失败。我们想要的相反的是,当闭包创建时执行 .clone()
fn needs_static<T: FnOnce() -> i32 + 'static>(f: T) -> i32 {
f()
}
let local: Rc<i32> = Rc::new(1);
// Actually capture a clone of the `Rc`
let cloned = local.clone();
let f = move || {
let in_closure = cloned;
*in_closure.as_ref()
};
// `f` is now `'static`!
assert_eq!(needs_static(f), 1);
// `local` has not been captured!
assert_eq!(*local.as_ref(), 1);
用法
宏 captures::capture 和 captures::capture_only 使用逗号分隔的 "捕获指令" 列表,最后是一个闭包表达式。一个捕获指令的例子是 clone x 指令,它表示应该捕获 x 的一个副本来代替 x。因此,上面的例子可以重写为
use captures::capture;
fn needs_static<T: FnOnce() -> i32 + 'static>(f: T) -> i32 {
f()
}
let local: Rc<i32> = Rc::new(1);
// Actually capture a clone of the `Rc`
let f = capture!(clone local,
move || {
let in_closure = local;
*in_closure.as_ref()
}
);
// `f` is still `'static`!
assert_eq!(needs_static(f), 1);
// `local` has not been captured!
assert_eq!(*local.as_ref(), 1);
捕获指令
目前支持以下捕获指令
clone x捕获x的一个副本。with x = expr捕获从expr计算得到的值x。all x捕获x的所有部分。从 Rust 2021 开始,在闭包中写入x.y将只会捕获x的y字段。指定all x将导致捕获x的所有部分。这不会影响x是按值还是按引用捕获 - 如果闭包是move闭包,它仍然会被按值捕获,如果它是一个非move闭包,编译器的标准推断算法将允许做出决定。
为了避免意外和编译错误,如果您指定了 clone 或 with 指令,那么此宏会将您的闭包转换为移动闭包,如果它尚未是移动闭包的话。因此,如果您的闭包是移动闭包 - 要么您明确将其标记为移动闭包,要么您使用了 with 或 clone 指令 - 您还可以指定这些指令
ref x以不可变引用方式捕获x。ref mut x以可变引用方式捕获x。
所有这些指令中的 x 必须仅是本地变量的名称。未来可能会支持更复杂的事情。目前也不支持组合指令。我将在我找到一种漂亮且一致的方式来处理它之后添加这个功能。
可变性
在 Rust 中,按值捕获的捕获变量继承它们所引用值的可变性。例如,
let a = 1; // immutable
let _ = move || {
a += 1;
a
};
无法编译,但如果 a 被标记为可变
let mut a = 1; // now mutable
let _ = move || {
a += 1;
a
};
则可以编译。
遗憾的是,这个包没有足够的信息来在一般情况下重现这种行为。 clone 和 with 指令创建新的变量,其可变性尚不明确。当前的政策是它们都默认为不可变。如果确定这不是最佳选择,将来可能会改变(显然要遵守 semver)。如果您希望这些值是可变的,可以通过在变量前加上 mut 来请求。例如,
let mut v = vec![1, 2]; // despite being mutable here
let _ = capture!(clone v, || {
v.push(3); // we cannot push to `v`, since it is not mutable
v
});
我们可以通过以下方式修复这个问题
let mut v = vec![1, 2];
let _ = capture!(clone mut v, || {
v.push(3); // we can push now
v
});
这仍然会发出警告,因为闭包外部的变量 v 的可变性未被使用。改写为 let v = vec![1, 2]; 代码将继续编译,并且不会发出警告。
all 指令不受此影响。在这种指令下捕获的变量,如果按值捕获,会正确继承其可变性。因此,这些指令不支持使用 mut 前缀。
捕获仅限
capture_only 宏的行为与 capture 宏完全相同,不同之处在于它还阻止了没有关联捕获指令的任何变量被捕获。例如,
let a = 1;
let mut b = 10;
let mut f = capture_only!(all a, || {
b += 1; // error
a + 1
});
assert_eq!(f(), 2);
assert_eq!(b, 11);
无法编译,错误信息指出没有本地变量 b。将 capture_only 改为 capture 将允许上述代码编译。如果您想表示 b 也可能被捕获,但又不想添加任何限制,可以添加一个 all 指令
let a = 1;
let mut b = 10;
let mut f = capture_only!(all a, all b, || {
b += 1; // compiles
a + 1
});
assert_eq!(f(), 2);
assert_eq!(b, 11);
依赖关系
~1.5MB
~34K SLoC