Rust函数返回类型中的Closure

3
我写了下面这个Rust程序,只打印出作为命令行参数的整数。它运行得非常完美:
use std::env;
fn main() {
    for i in env::args().filter_map(|arg| arg.parse::<i32>().ok()) {
        println!("{}", i);
    }
}

我尝试将程序重写成一个抽象函数来过滤数据。然而,这个版本无法编译。

use std::env::Args;
use std::env;
use std::iter::FilterMap;
// Version 2
fn main() {
    for i in nums(&env::args()) {
        println!("{}", i);
    }
}

fn nums<F: Fn(String) -> Option<i32>>(args: &Args) -> FilterMap<Args,F> {
    args.filter_map(|arg| arg.parse::<i32>().ok())
}

它会产生以下编译错误:
   Compiling iterator_return_type v0.1.0 (file:///Users/gabriel/AllProjects/SentimentAnalysis/iterator_return_type)
error[E0282]: type annotations needed
  --> src/main.rs:16:9
   |
16 |     for i in nums(&env::args()) {
   |         ^ cannot infer type for `_`

error: the type of this value must be known in this context
  --> src/main.rs:22:27
   |
22 |     args.filter_map(|arg| arg.parse::<i32>().ok())
   |                           ^^^^^^^^^^^^^^^^^^

error[E0308]: mismatched types
  --> src/main.rs:22:21
   |
22 |     args.filter_map(|arg| arg.parse::<i32>().ok())
   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter, found closure
   |
   = note: expected type `F`
              found type `[closure@src/main.rs:22:21: 22:50]`

error: aborting due to previous error(s)

error: Could not compile `iterator_return_type`.

我发现特别困惑的是最终的编译错误。我不知道该如何指定其他的闭包类型。

谢谢!


那个问题中给出的解决方案对我的示例无效。在那个问题中,Split迭代器是返回类型。FilterMap迭代器需要一个闭包类型参数。这是完全不同的问题。 - Gabriel Ferrer
你应该认真考虑修改问题的标题,因为它很大程度上表明这是上面建议的重复。你似乎更多地遇到了“无法引用闭包类型”的问题。 - E net4
好的建议,谢谢!我已经这样做了。 - Gabriel Ferrer
@GabrielFerrer,这是同样的问题。编译器无法通过查看函数代码来推断返回类型,因此您必须自己提供完整的返回类型,但是您无法这样做,因为闭包的类型是匿名的。因此,您需要采用我指出的答案中的方法。例如,像boxed trait-object - red75prime
2
虽然答案中提到的解决方案可行,但它并没有提及闭包,特别是没有解释关键问题,即对于迭代器而言这只是语法糖,但对于闭包来说却是必须的,因为它们是伏地魔类型。 - Matthieu M.
1个回答

4

impl TraitBox<Trait>的解决方案都可以应用于迭代器和闭包,它们都只是特征!区别在于,在闭包的情况下,您必须使用它们。

如果您想使用impl Trait,那么您的代码将如下所示(请注意,Args应按值传递):

#![feature(conservative_impl_trait)]

use std::env::Args;
use std::env;
use std::iter::FilterMap;

fn main() {
    for i in nums(env::args()) {
        println!("{}", i);
    }
}

fn nums(args: Args) -> FilterMap<Args, impl FnMut(String) -> Option<i32>> {
    args.filter_map(|arg| arg.parse::<i32>().ok())
}

然而,通常您不需要暴露迭代器类型的细节;因此,您可以采用以下方式:

fn nums(args: Args) -> impl Iterator<Item = i32> {
    args.filter_map(|arg| arg.parse::<i32>().ok())
}

如果你想使用稳定版的Rust,那么目前不得不使用boxing技术。
fn nums(args: Args) -> Box<Iterator<Item = i32>> {
    Box::new(args.filter_map(|arg| arg.parse::<i32>().ok()))
}

为什么你无法描述完整的闭包类型,尽管你可以像 Zip<Drain<'a, i32>, IntoIter<&'b str>> 一样描述迭代器?原因有两个:
  • 闭包类型本质上是匿名的;如果想要返回它们,你必须将其匿名化 (impl Fn()) 或封箱 (Box<Fn()>)。
  • 闭包特性的接口不稳定;你不能稳定地实现它们 (impl Fn() for YourType { .. })。
那么为什么你的代码不起作用呢?原因是:
  • 如果你想将闭包传递给一个函数,调用者 确定其类型。在这种情况下,你可以写成 fn foo<T: Fn()>() { .. }
  • 如果你想从一个函数中传递闭包,被调用者 确定其类型。在这种情况下,你需要使用 impl Trait
RFC 1951 将改变这种区别。您将能够在这两种情况下使用 impl Trait

另一个出色的答案!我想欢迎您加入Rust标签,并期待您未来的贡献!关于“匿名”类型的问题,编程社区似乎正在将这些不应被命名(实际上无法)的类型称为“伏地魔类型”。 - Matthieu M.

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