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