高级函数与闭包

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_one5 调用 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 参数,并返回一个 i32fn。最后咱们便可以在 do_twice 函数体中调用 f 了。在 main 中,咱们可以将名为 add_one 的函数,作为首个参数传递给 do_twice

与闭包不同,fn 是种类型而非一个特质,因此咱们将 fn 直接指定为参数类型,而非使用 Fn 特质之一,作为特质边界声明一个泛型参数。

函数指针实现了全部三个闭包特质(FnFnMutFnOnce),意味着咱们可以一直将某个函数,作为期望得到一个闭包的函数的参数而加以传递。编写出使用了一个泛型及闭包特质之一的函数,是最佳做法,如此咱们的函数就既可以接收函数,也可以接收闭包了。

那就是说,一种咱们只想接收 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 章中的 “使用特质对象实现不同类型值” 小节。

接下来,咱们就要看看宏了!

Last change: 2023-12-01, commit: e3b5cc8