如何实现等同于嵌套的`impl Trait`的功能?

7

函数A接受一个函数B作为参数,而函数B又接受函数C作为参数。我尝试了以下类似的语法,但是出现了错误:

fn a(b: impl Fn(impl Fn() -> ()) -> ()) -> () {
 // ...
}  

error[E0666]: nested `impl Trait` is not allowed
 --> src/main.rs:2:21
  |
2 |     fn a(b: impl Fn(impl Fn() -> ()) -> ()) -> () {
  |             --------^^^^^^^^^^^^^^^-------
  |             |       |
  |             |       nested `impl Trait` here
  |             outer `impl Trait`

由于某些原因,我无法使用&dyn关键字:

fn a(b: impl Fn(&dyn Fn() -> ()) -> ()) -> () {
 // ...
} 

有没有其他的方法来做这件事?

我不知道为什么嵌套的impl Trait会导致错误。


你的第二个代码片段在 playground 中可以运行。也许需要更新编译器? - Sebastian Redl
我知道它可以工作,但在我的特殊情况下,我不能使用&dyn关键字,我需要在新线程中调用函数C,否则会导致错误:cannot be shared between threads safely - 周汉成
1个回答

14

嵌套的impl Trait不能工作,因为它尚未被指定或实现。此外,由于其他Rust部分的工作方式,它并不是非常有用。

fn a<F, G>(b: F) -> () where F: Fn(G), G: Fn() -> ()可以使用完整的泛型语法编写。

fn a<B, C>(b: B) -> ()
where B: Fn(C) -> (),
      C: Fn() -> ()

那么这意味着什么呢?这意味着B是可调用的,它需要一个其他可调用对象作为参数。但重要的是具体类型。具体来说,B不接受任何兼容签名的可调用对象,而是专门需要一个C。那么C是什么?这就是问题所在。如果像这样调用a:
a(|f| f());

那么C就是lambda表达式参数f的类型,但该类型是未知的,因为参数f的类型无法仅从使用中推断出来。但假设这不是问题,a的主体会是什么样子?

fn a<B, C>(b: B) -> ()
where B: Fn(C) -> (),
      C: Fn() -> () {
  b(|| ())
}

在这里,我们尝试通过lambda调用b。但是lambda的类型是“未命名本地lambda类型”,而不是C。由于C是从外部传入的,它不能成为局部于a的lambda的类型。简而言之,除非您将C作为附加参数传递给a,否则a没有任何内容可以传递给b
显然,您想要的是B不是一个可以使用某些C调用的函数对象,而是可以使用任何C调用的多态函数对象。Rust不支持编译时多态函数对象(Haskell中的等效物将是forall a. a -> IO ()或类似物)。它只支持运行时多态函数对象。
这就是dyn的工作。现在您已经说过您不能使用&dyn,因为您想将函数对象传递给另一个线程。所以,使用Box<dyn>代替:
fn a(b: impl Fn(Box<dyn Fn() -> ()>) -> ()) -> () {
 b(Box::new(|| println!("Hello")))
} 

fn main() {
    a(move |f| { f(); f(); });
}

1
作为一条注释:每当一个函数(或者甚至是 Fn)返回 () 时,你可以省略它,例如,你上面的函数 a 可以变成 fn a<B, C>(b: B) where B: Fn(C), C: Fn() - hellow
1
此外,这里的 where 表单比假设的嵌套 impl 表单可读性高出几个数量级。请不要仅仅因为你可以而试图尽可能压缩你的代码... - mcarton
1
@hellow 它不起作用:playground - 周汉成
考虑到 Rust 的其他部分是如何工作的,它并不是非常有用。我不确定这个说法。但我觉得它可能有助于解决 在新线程中调用递归闭包 这个问题。 - 周汉成
你的例子用 -> () 也不起作用,那有什么意义呢? - hellow
啊,我错了。在这里,“那个”可以省略,它指的是括号而不是答案。 - 周汉成

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