在 Rust 中调用存储在结构中的堆栈分配闭包
Calling a Stack-Allocated Closure Stored in a Struct in Rust
我在这样的结构中存储闭包:
#[derive(Clone)]
struct S<'a> {
func: &'a FnOnce() -> u32
}
fn main() {
let s = S { func: &|| 0 };
let val = (s.func)();
println!("{}", val);
}
我编译的时候,s.func
不能移动到自己执行。我明白为什么它不能移动(即它只是一个参考,它的大小在编译时是未知的),但根本不知道为什么它被移动——仅仅是因为闭包是通过特征实现的吗?
错误信息如下:
error[E0161]: cannot move a value of type std::ops::FnOnce() -> u32:
the size of std::ops::FnOnce() -> u32 cannot be statically determined
--> main.rs:8:15
|
8 | let val = (s.func)();
| ^^^^^^^^
error[E0507]: cannot move out of borrowed content
--> main.rs:8:15
|
8 | let val = (s.func)();
| ^^^^^^^^ cannot move out of borrowed content
error: aborting due to 2 previous errors
这是将闭包存储在堆上的唯一解决方法吗(通过 Box<FnOnce() -> u32>
)?为什么调用闭包会移动它?大概调用它不会改变函数本身。
正在移动闭包,因为 FnOnce::call_once
按值取用 self
。此合同强制保证该函数不会被多次调用。
如果你确实最多调用一次闭包,并且你想使用 FnOnce
特性,那么你的结构需要获得该闭包的所有权(并且你需要使你的结构通用在闭合类型上)。请注意,调用闭包会将闭包移出您的结构,从而使整个结构无效;您可以通过将 FnOnce
包装在 Option
中并 take
在调用它之前将闭包从 Option
中取出来解决这个问题。
如果您可能多次调用闭包,您不想获得闭包的所有权,或者您不想让您的结构在闭包类型上通用,那么您应该使用 Fn
或 FnMut
代替。 Fn::call
takes self
by reference and FnMut::call_mut
通过可变引用获取 self
。由于两者都接受引用,因此您可以对它们使用特征对象。
正如 Francis 所解释的那样,声明一个闭包 FnOnce
告诉 Rust 你接受最广泛的 class 闭包,包括那些耗尽它们捕获的对象的闭包。编译器通过在调用时销毁闭包对象本身(通过将其移动到它自己的 call
方法中)来确保此类闭包仅被调用一次。
可以使用 FnOnce
并且在闭包上仍然没有 S
泛型,但是它需要一些工作来设置,这样闭包就不可能被调用超过一次:
- 闭包必须存储在一个
Option
中,所以它的内容可以是"stolen"并且Option
替换为None
(这部分确保闭包赢了'被调用两次);
- 发明一个特征,知道如何从选项中窃取闭包并调用它(或者如果闭包已经被盗则做其他事情);
- 在
S
中存储对特征对象的引用 - 这使得相同的 S
类型可以在不同的闭包上工作,而不是在闭包类型上通用。
结果如下所示:
trait Callable {
fn call_once_safe(&mut self, default: u32) -> u32;
}
impl<F: FnOnce() -> u32> Callable for Option<F> {
fn call_once_safe(&mut self, default: u32) -> u32 {
if let Some(func) = self.take() {
func()
} else {
default
}
}
}
struct S<'a> {
func: &'a mut Callable
}
impl<'a> S<'a> {
pub fn invoke(&mut self) -> u32 {
self.func.call_once_safe(1)
}
}
fn main() {
let mut s = S { func: &mut Some(|| 0) };
let val1 = s.invoke();
let val2 = s.invoke();
println!("{} {}", val1, val2);
}
唯一知道闭包细节的地方是特定 Option<F>
的 Callable
实现,为每个闭包生成并由 &mut Callable
胖指针的 vtable 引用在 S
.
中初始化 func
时创建
我在这样的结构中存储闭包:
#[derive(Clone)]
struct S<'a> {
func: &'a FnOnce() -> u32
}
fn main() {
let s = S { func: &|| 0 };
let val = (s.func)();
println!("{}", val);
}
我编译的时候,s.func
不能移动到自己执行。我明白为什么它不能移动(即它只是一个参考,它的大小在编译时是未知的),但根本不知道为什么它被移动——仅仅是因为闭包是通过特征实现的吗?
错误信息如下:
error[E0161]: cannot move a value of type std::ops::FnOnce() -> u32:
the size of std::ops::FnOnce() -> u32 cannot be statically determined
--> main.rs:8:15
|
8 | let val = (s.func)();
| ^^^^^^^^
error[E0507]: cannot move out of borrowed content
--> main.rs:8:15
|
8 | let val = (s.func)();
| ^^^^^^^^ cannot move out of borrowed content
error: aborting due to 2 previous errors
这是将闭包存储在堆上的唯一解决方法吗(通过 Box<FnOnce() -> u32>
)?为什么调用闭包会移动它?大概调用它不会改变函数本身。
正在移动闭包,因为 FnOnce::call_once
按值取用 self
。此合同强制保证该函数不会被多次调用。
如果你确实最多调用一次闭包,并且你想使用 FnOnce
特性,那么你的结构需要获得该闭包的所有权(并且你需要使你的结构通用在闭合类型上)。请注意,调用闭包会将闭包移出您的结构,从而使整个结构无效;您可以通过将 FnOnce
包装在 Option
中并 take
在调用它之前将闭包从 Option
中取出来解决这个问题。
如果您可能多次调用闭包,您不想获得闭包的所有权,或者您不想让您的结构在闭包类型上通用,那么您应该使用 Fn
或 FnMut
代替。 Fn::call
takes self
by reference and FnMut::call_mut
通过可变引用获取 self
。由于两者都接受引用,因此您可以对它们使用特征对象。
正如 Francis 所解释的那样,声明一个闭包 FnOnce
告诉 Rust 你接受最广泛的 class 闭包,包括那些耗尽它们捕获的对象的闭包。编译器通过在调用时销毁闭包对象本身(通过将其移动到它自己的 call
方法中)来确保此类闭包仅被调用一次。
可以使用 FnOnce
并且在闭包上仍然没有 S
泛型,但是它需要一些工作来设置,这样闭包就不可能被调用超过一次:
- 闭包必须存储在一个
Option
中,所以它的内容可以是"stolen"并且Option
替换为None
(这部分确保闭包赢了'被调用两次); - 发明一个特征,知道如何从选项中窃取闭包并调用它(或者如果闭包已经被盗则做其他事情);
- 在
S
中存储对特征对象的引用 - 这使得相同的S
类型可以在不同的闭包上工作,而不是在闭包类型上通用。
结果如下所示:
trait Callable {
fn call_once_safe(&mut self, default: u32) -> u32;
}
impl<F: FnOnce() -> u32> Callable for Option<F> {
fn call_once_safe(&mut self, default: u32) -> u32 {
if let Some(func) = self.take() {
func()
} else {
default
}
}
}
struct S<'a> {
func: &'a mut Callable
}
impl<'a> S<'a> {
pub fn invoke(&mut self) -> u32 {
self.func.call_once_safe(1)
}
}
fn main() {
let mut s = S { func: &mut Some(|| 0) };
let val1 = s.invoke();
let val2 = s.invoke();
println!("{} {}", val1, val2);
}
唯一知道闭包细节的地方是特定 Option<F>
的 Callable
实现,为每个闭包生成并由 &mut Callable
胖指针的 vtable 引用在 S
.
func
时创建