高级函数和闭包
本节探讨与函数和闭包相关的一些高级特性,包括函数指针和返回闭包。
函数指针
我们已经讨论过如何将闭包传递给函数;您也可以将常规函数传递给函数!当您想传递已定义的函数而不是定义新的闭包时,此技术很有用。函数强制转换为 fn
类型(带小写字母 f),不要与 Fn
闭包 trait 混淆。fn
类型称为*函数指针*。使用函数指针传递函数将允许您将函数用作其他函数的参数。
指定参数为函数指针的语法与闭包的语法类似,如清单 19-27 所示,其中我们定义了一个函数 add_one
,它为其参数加一。函数 do_twice
接受两个参数:一个指向任何接受 i32
参数并返回 i32
的函数的函数指针,以及一个 i32
值。do_twice
函数调用函数 f
两次,将 arg
值传递给它,然后将两个函数调用结果加在一起。main
函数使用参数 add_one
和 5
调用 do_twice
。
文件名: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}"); }
此代码打印 答案是:12
。我们指定 do_twice
中的参数 f
是一个 fn
,它接受一个 i32
类型的参数并返回一个 i32
。然后我们可以在 do_twice
的主体中调用 f
。在 main
中,我们可以将函数名 add_one
作为第一个参数传递给 do_twice
。
与闭包不同,fn
是一种类型而不是 trait,因此我们直接将 fn
指定为参数类型,而不是使用 Fn
trait 之一作为 trait 绑定来声明泛型类型参数。
函数指针实现了所有三个闭包 trait(Fn
、FnMut
和 FnOnce
),这意味着您始终可以将函数指针作为参数传递给期望闭包的函数。最好使用泛型类型和其中一个闭包 trait 编写函数,以便您的函数可以接受函数或闭包。
也就是说,您只想接受 fn
而不接受闭包的一个例子是与没有闭包的外部代码交互时:C 函数可以接受函数作为参数,但 C 没有闭包。
作为可以使用内联定义的闭包或命名函数的示例,让我们看一下标准库中 Iterator
trait 提供的 map
方法的用法。要使用 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(); }
请注意,我们必须使用我们在前面的 “高级 Trait”部分中讨论过的完全限定语法,因为有多个名为 to_string
的函数可用。在这里,我们使用的是 ToString
trait 中定义的 to_string
函数,标准库已经为任何实现了 Display
的类型实现了该函数。
回想一下第 6 章的 “枚举值”部分,我们定义的每个枚举变体的名称也成为一个初始化函数。我们可以将这些初始化函数用作实现闭包 trait 的函数指针,这意味着我们可以将初始化函数指定为接受闭包的方法的参数,如下所示
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
实例。有些人喜欢这种风格,有些人喜欢使用闭包。它们编译成相同的代码,所以使用对你来说更清晰的风格。
返回闭包
闭包由 trait 表示,这意味着您不能直接返回闭包。在大多数情况下,您可能希望返回 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: return an `impl Trait` instead of a `dyn Trait`, if all returned values are the same type
|
1 | fn returns_closure() -> impl Fn(i32) -> i32 {
| ~~~~
help: 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 不知道它需要多少空间来存储闭包。我们之前看到了这个问题的解决方案。我们可以使用 trait 对象
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
这段代码可以编译得很好。有关 trait 对象的更多信息,请参阅第 17 章的 “使用允许不同类型值的 Trait 对象”部分。
接下来,让我们看看宏!