当移动一个被包装的函数时,出现“无法移动类型为FnOnce的值”的错误。

12

我正在尝试在Rust中进行高阶编程,但在处理闭包时遇到了一些困难。以下是一个代码片段,说明了我遇到的问题之一:

pub enum Foo {
    Bar(Box<FnOnce(i32)>),
}

pub fn app(i: i32, arg: Foo) {
    match arg {
        Foo::Bar(f) => f(i),
    }
}

当我编译这段代码时,出现以下错误信息:

error[E0161]: cannot move a value of type std::ops::FnOnce(i32) + 'static: the size of std::ops::FnOnce(i32) + 'static cannot be statically determined
 --> src/main.rs:7:24
  |
7 |         Foo::Bar(f) => f(i),
  |                        ^

既然我把函数放在了一个Box中,我本以为那样就能解决编译器不知道大小的问题。我该如何让上述程序编译通过呢?

2个回答

18

这里是FnOnce特征的定义(稍微简化了一些):

pub trait FnOnce<Args> {
    type Output;

    fn call_once(self, args: Args) -> Self::Output;
}

要调用FnOnce闭包,需要能够将闭包值本身移动到调用中。请注意,self必须是实际的闭包类型;Box<dyn FnOnce>则是完全不同的类型。

Rust 1.35

Box<dyn FnOnce>现在可以被调用;您的原始代码仍可以如原样工作。

先前版本

标准库中有一种类型可用于解决此情况:FnBox。但不幸的是,它不稳定。

您的选择是:

  • 重构代码,以保留实际的闭包类型,而不是使用Box<FnOnce>
  • 改用Box<FnMut>
  • 等待FnBox稳定。
  • 转用夜间编译器。

谢谢回答。对我来说,使用实际的闭包类型不是一个选项,因为我需要能够在枚举中存储不同的闭包。我不知道如何使Box<FnMut>建议起作用,当我尝试时会出现错误。相反,我所做的就是只说Box<fn(i32)>。这帮助我编译了上面的例子。然而,我无法使用该类型创建枚举,因为它与闭包的类型不匹配。所以我想我只能尝试使用夜间编译器了。 - svenningsson
@svenningsson:使用Box<FnMut(i32)>代替;因为FnMut是“对象安全”的,所以您可以通过Box(或任何间接方式)调用它。唯一不能做的事情是让闭包消耗值(尽管您可以通过Option解决这个问题)。 - DK.
1
@svenningsson:FnMut需要在调用时能够改变闭包的值,因此请使用Foo::Bar(mut f) => f(i)来使f绑定可变。 - DK.
好的,谢谢你。这让我的示例编译成功了。不幸的是,使用FnMut阻止我创建我喜欢的闭包类型,但这是一个不同的问题,也许我可以找到一个解决办法。 - svenningsson
只是想确认一下FnBox解决了我的问题。再次感谢! - svenningsson
显示剩余2条评论

2

目前来看,FnBox稳定的可能性不大,但是可以将 F: FnOnce(...) -> ...Option<F> 包装,将其绑定在可变闭包中,然后在内部解包并调用它(这样如果它被调用超过一次就会panic)。生成的闭包可以封装成 Box<FnMut(...) -> ...>,你可能需要以某种方式对其进行包装,以确保它仅被使用("调用")一次。

可以参见我的boxfnonce crate。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接