高级函数和闭包


本节探讨了与函数和闭包相关的一些高级功能,包括函数指针和返回闭包。


函数指针


我们已经讨论了如何将闭包传递给函数;你也可以将常规函数传递给函数!当你想传递已经定义的函数而不是定义新的闭包时,这种技术很有用。函数强制为类型 fn(带小写 f),不要与 Fn 混淆 Closure trait 的 Trait 中。fn 类型称为函数指针。使用函数指针传递函数将允许您将函数用作其他函数的参数。


指定参数为函数指针的语法类似于 闭包的 VALUE,如示例 19-27 所示,其中我们定义了一个函数 add_one,则向其参数添加 1。函数 do_twice 采用两个参数:一个函数指针指向任何采用 i32 参数并返回 i32 的函数,以及一个 i32 值。do_twice 函数调用函数 f 两次,向其传递 arg 值,然后将两个函数调用结果相加。函数使用参数调用 do_twice add_one5.


文件名: src/main.rs

fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}

fn main() {
    let answer = do_twice(add_one, 5);

    println!("The answer is: {answer}");
}


示例 19-27:使用 fn 类型接受函数指针作为参数


这段代码打印的答案是:12。我们指定 do_twice 是一个 fn,它采用一个 i32 类型的参数并返回一个 i32.然后我们可以在 do_twice 的主体中调用 f。在 main 中,我们可以将函数名称 add_one 作为第一个参数传递给 do_twice


与闭包不同,fn 是一种类型而不是 trait,因此我们直接指定 fn 作为参数类型,而不是声明一个泛型类型参数,并将其中一个 Fn trait 作为 trait 绑定。


函数指针实现了所有三个闭包特征(FnFnMutFnOnce),这意味着你始终可以将函数指针作为需要闭包的函数的参数传递。最好使用泛型类型和其中一个闭包特征编写函数,这样你的函数就可以接受函数或闭包。


也就是说,你只想接受 fn 而不接受闭包的一个例子是与没有闭包的外部代码交互时: C 函数可以接受函数作为参数,但 C 没有闭包。


作为可以使用内联定义的闭包或命名函数的示例,让我们看看 Iterator 提供的 map 方法的用法 trait 中。要使用 map 函数将数字向量转换为字符串向量,我们可以使用闭包,如下所示:

fn main() {
    let list_of_numbers = vec![1, 2, 3];
    let list_of_strings: Vec<String> =
        list_of_numbers.iter().map(|i| i.to_string()).collect();
}


或者我们可以将函数命名为 map 的参数而不是闭包,如下所示:

fn main() {
    let list_of_numbers = vec![1, 2, 3];
    let list_of_strings: Vec<String> =
        list_of_numbers.iter().map(ToString::to_string).collect();
}


请注意,我们必须使用我们之前在 “高级特征” 部分中讨论的完全限定语法,因为有多个名为 to_string 的函数可用。在这里,我们使用 to_string ToString trait 中定义的函数,标准库已为实现 Display 的任何类型的函数实现了该函数。


回想一下第 6 章的 “Enum values” 部分,我们定义的每个枚举变体的名称也成为一个初始化器函数。我们可以将这些初始化器函数用作实现 closure trait 的函数指针,这意味着我们可以将初始化器函数指定为接受 closure 的方法的参数,如下所示:

fn main() {
    enum Status {
        Value(u32),
        Stop,
    }

    let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();
}


在这里,我们使用 Status::Value 的初始化器函数,使用调用 map 的范围中的每个 u32 值创建 Status::Value 实例。有些人喜欢这种风格,有些人喜欢使用闭包。它们编译为相同的代码,因此请使用您更清楚的样式。


返回闭包


Closure 由 traits 表示,这意味着您不能直接返回 Closure。在大多数情况下,您可能希望返回 trait,您可以改用实现 trait 的具体类型作为函数的返回值。但是,你不能用闭包来做到这一点,因为它们没有可返回的具体类型;例如,不允许将函数指针 FN 用作返回类型。


以下代码尝试直接返回一个闭包,但它不会编译:

fn returns_closure() -> dyn Fn(i32) -> i32 {
    |x| x + 1
}


编译器错误如下:

$ cargo build
   Compiling functions-example v0.1.0 (file:///projects/functions-example)
error[E0746]: return type cannot have an unboxed trait object
 --> src/lib.rs:1:25
  |
1 | fn returns_closure() -> dyn Fn(i32) -> i32 {
  |                         ^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
  |
help: consider returning an `impl Trait` instead of a `dyn Trait`
  |
1 | fn returns_closure() -> impl Fn(i32) -> i32 {
  |                         ~~~~
help: alternatively, box the return type, and wrap all of the returned values in `Box::new`
  |
1 ~ fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
2 ~     Box::new(|x| x + 1)
  |

For more information about this error, try `rustc --explain E0746`.
error: could not compile `functions-example` (lib) due to 1 previous error


该错误再次引用了 Sized trait!Rust 不知道需要多少空间来存储 closure。我们之前看到了这个问题的解决方案。我们可以使用 trait 对象:

fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}


这段代码编译得很好。有关 trait 对象的更多信息,请参阅“使用允许不同值的 trait 对象”部分 类型”。


接下来,让我们看看宏!