调用存储在 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中取出来解决此问题。

如果您可能多次调用闭包,您不想获得闭包的所有权,或者您不想使结构在闭包类型上泛型,则应改用FnFnMutFn::call通过引用获取selfFnMut::call_mut通过可变引用获取self。由于两者都接受引用,因此您可以将 trait 对象与它们一起使用。

正如 Francis 所解释的那样,声明闭包FnOnce告诉 Rust 你接受最广泛的闭包类别,包括那些耗尽它们捕获的对象。编译器通过在调用时销毁闭包对象本身(通过将其移动到自己的call方法中)来确保此类闭包只调用一次。

可以使用FnOnce,但仍然没有S泛型的闭包,但是需要一些工作来设置,以便闭包不能多次调用:

  • 闭包必须存储在Option中,因此其内容可以被"窃取",并将Option替换为None(这部分确保闭包不会被调用两次);
  • 发明一个特征,知道如何从选项中窃取闭包并调用它(或者如果闭包已经被盗,则做其他事情);
  • S中存储对 trait 对象的引用 - 这使得相同的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实现,为每个闭包生成,并由初始化Sfunc时创建的&mut Callable胖指针的 vtable 引用。

最新更新