高级函数与闭包
Advanced Functions and Closures
这个小节会探讨一些与函数和闭包有关的高级特性,包括函数指针与作为返回值的闭包,function pointers and returning closures。
函数指针
Function Pointers
咱们已讲到了怎样把闭包传递给函数;咱们也可以把常规函数传递给函数!在咱们打算传递一个咱们已定义的函数,而非定义出一个新闭包时,这种技巧便是有用的。这些函数会强制转换到类型 fn
(有着小写的 f
),而不会与那个 Fn
闭包特质混淆。这个 fn
类型,被称为 函数指针,funciton pointer。使用函数指针的传递函数,将实现把函数作为其他函数参数而运用。
指明某个函数是个函数指针的语法,与参数是个闭包的语法类似,如下清单 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! ("答案为:{}", answer); }
清单 19-27:使用 fn
类型来以参数方式接收函数指针
此代码会打印出 答案为:12
。咱们指明了 do_twice
中的参数 f
是取一个类型 i32
参数,并返回一个 i32
的 fn
。最后咱们便可以在 do_twice
函数体中调用 f
了。在 main
中,咱们可以将名为 add_one
的函数,作为首个参数传递给 do_twice
。
与闭包不同,fn
是种类型而非一个特质,因此咱们将 fn
直接指定为参数类型,而非使用 Fn
特质之一,作为特质边界声明一个泛型参数。
函数指针实现了全部三个闭包特质(Fn
、FnMut
与 FnOnce
),意味着咱们可以一直将某个函数,作为期望得到一个闭包的函数的参数而加以传递。编写出使用了一个泛型及闭包特质之一的函数,是最佳做法,如此咱们的函数就既可以接收函数,也可以接收闭包了。
那就是说,一种咱们只想接收 fn
而不想接收闭包的情况,便是与并无闭包的外部代码相交互时:C 语言函数可以参数方式接收函数,但 C 语言是没有闭包的。
而作为既可以使用内联定义的闭包,又可以使用命名函数的一种情况,下面就来看看标准库中 Iterator
特质所提供的 map
函数的一种用法。要使用 map
函数来将某个一些数字构成的矢量值,转换为字符串的矢量,咱们可以使用一个闭包,如下面这样:
#![allow(unused)] 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
参数的函数,而非那个闭包,如下面这样:
#![allow(unused)] 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
函数,因此咱们就必须使用早先在 “高级特质” 小节中讲到的完全合格语法。这里咱们使用了那个标准库已对任何实现了 Display
类型,实现过了的 ToString
特质中的 to_string
函数。
自第 6 章 “枚举取值” 小节,回顾咱们所定义的各个枚举变种名字,也会成为一个初始化函数。咱们可以将这些初始化函数,作为实现了那些闭包特质的函数指针使用,这就意味着咱们可以把这些初始化函数,指定为取闭包的方法的参数,像下面这样:
#![allow(unused)] 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
的实例。有的人会首选这种方式,而别的人则首选闭包。他们会编译到同样的代码,因此请使用你认为更清晰的风格。
返回闭包
Returning Closures
闭包是由特质表示的,这就意味着咱们不能直接返回闭包。在多数咱们可能打算返回特质的情形中,咱们都可以转而使用实现了该特质的具体类型,作为函数的返回值。但是,由于闭包没有可返回的具体类型,因此对于闭包是不能这样做的;就好比咱们是不被允许将函数指针作为返回值类型。
下面的代码尝试直接返回一个闭包,但其不会编译:
#![allow(unused)] fn main() { fn returns_closure() -> dyn Fn(i32) -> i32 { |x| x + 1 } }
编译器报错如下:
$ cargo build
Compiling returning_closure v0.1.0 (/home/lenny.peng/rust-lang/returning_closure)
error[E0746]: return type cannot have an unboxed trait object
--> src/main.rs:1:25
|
1 | fn returns_closure() -> dyn Fn(i32) -> i32 {
| ^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
help: use `impl Fn(i32) -> i32` as the return type, as all return paths are of type `[closure@src/main.rs:2:5: 2:8]`, which implements `Fn(i32) -> i32`
|
1 | fn returns_closure() -> impl Fn(i32) -> i32 {
| ~~~~~~~~~~~~~~~~~~~
For more information about this error, try `rustc --explain E0746`.
error: could not compile `returning_closure` due to previous error
这个报错再度指向了那个 Sized
特质!Rust 不清楚他将需要多少内存空间来存储这个闭包。早先咱们就已见到了对这个问题的解决办法了。咱们可以使用一个特质对象:
#![allow(unused)] fn main() { fn returns_closure() -> Box<dyn Fn(i32) -> i32> { Box::new(|x| x + 1) } }
这段代码可以很好地编译。有关特质对象的更多内容,请参考第 17 章中的 “使用特质对象实现不同类型值” 小节。
接下来,咱们就要看看宏了!