rust 快速入门——20 高级函数与闭包

64 阅读7分钟

[!|center] 普若哥们儿

github.com/wu-hongbing…

gitee.com/wuhongbing/…

高级函数与闭包

函数指针

fn 关键字既用于定义函数,也用于定义函数指针。注意不要与闭包 trait 的 Fn 相混淆。下面示例中 Binop 被定义为函数指针类型:

#![allow(unused)]
fn main() {
    fn add(x: i32, y: i32) -> i32 {
        x + y
    }

    let mut x = add(5, 7);

    type Binop = fn(i32, i32) -> i32;
    let bo: Binop = add;
    x = bo(5, 7);
}
函数指针作为函数参数

函数指针可以作为另一个函数的参数。指定参数为函数指针的语法类似于闭包,如下例所示,这里定义了一个 add_one 函数将其参数加 1。do_twice 函数获取两个参数:一个指向任何获取一个 i32 参数并返回一个 i32 的函数指针,和一个 i32 值。do_twice 函数传递 arg 参数调用 f 函数两次,接着将两次函数调用的结果相加。main 函数使用 add_one5 作为参数调用 do_twice

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);
}

不同于闭包,fn 是一个类型而不是一个 trait,所以直接指定 fn 作为参数而不是声明一个带有 Fn 作为 trait bound 的泛型参数。

函数指针实现了所有三个闭包 trait(FnFnMutFnOnce),所以总是可以在调用闭包作为参数的函数时传递函数指针作为参数。比如:

// 定义一个函数,可以接受一个由 `Fn` 限定trait的泛型 `F` 参数并调用它
fn call_me<F: Fn()>(f: F) {
    f()
}

// 定义一个满足 `Fn` 约束的函数
fn function() {
    println!("I'm a function!");
}

fn main() {
    // 定义一个满足 `Fn` 约束的闭包
    let closure = || println!("I'm a closure!");

    call_me(closure);  // 闭包作为参数
    call_me(function); // 函数指针作为参数
}

因为 function()Fn trait 约束的函数,其内部不可有 &mut 和获取所有权的操作。

标准库中的 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();
}

使用函数指针作为参数:

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();
}

这里使用了定义于 ToString trait 的 to_string 函数,标准库为所有实现了 Display 的类型实现了这个 trait。

Rust 每一个枚举成员也是一个构造函数。我们可以使用这些构造函数作为实现了闭包 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 的初始化函数。

采用泛型,函数既可以接受闭包,也可以接受函数指针作为参数;明确指定参数为函数指针为参数的函数只能接受函数指针,不能接受闭包为参数:

// 采用泛型,可以接收闭包或函数指针作为参数
fn apply_1<F>(f: F) -> i32
where
    F: FnOnce(i32) -> i32,
{
    f(1)
}
// 函数指针作为参数,闭包不能作为参数!
fn apply_2(f: fn(i32) -> i32) -> i32 {
    f(2)
}

fn function(x: i32) -> i32 {
    x * 2
}

fn main() {
    let x = 5;

    // 创建一个闭包,捕获环境变量'x',如果闭包不捕获环境变量,则可以强制转换为函数,否则不可以
    let closure = |y| y * x;

    apply_1(closure);
    apply_1(function);
    // apply_2(closure); //不合法,apply_2只能接收函数指针作为参数,如果closure参数不捕获环境变量,则可以强制转换为函数
    apply_2(function);
}

第 25 行不合法,根据 apply_2 的定义,它只能接收函数指针作为参数。如果实参 closure 闭包不捕获环境变量,则可以强制转换为函数,则第 25 行是合法的。

只接受函数指针,而不接受闭包作为参数的情况通常是与不存在闭包的外部代码交互:C 语言的函数可以接受函数作为参数,但 C 语言没有闭包。

函数指针作为函数返回值

函数指针也可以作为函数返回值:

fn returns_function() -> fn(i32) -> i32 {
    fn function(x: i32) -> i32 {
        x + 1
    }
    function
}
fn main() {
    let x = 5;
    println!("{}",returns_function()(x));

}

再比如:

fn add(x: i32, y: i32) -> i32 {
    x + y
}

fn subtract(x: i32, y: i32) -> i32 {
    x - y
}

fn get_operation(op: &str) -> fn(i32, i32) -> i32 {
    match op {
        "add" => add,
        "subtract" => subtract,
        _ => panic!("Unknown operation"),
    }
}

fn main() {
    let add_fn = get_operation("add");
    let result1 = add_fn(10, 20);
    println!("Result1: {}", result1); // 输出:Result1: 30

    let subtract_fn = get_operation("subtract");
    let result2 = subtract_fn(20, 10);
    println!("Result2: {}", result2); // 输出:Result2: 10
}

返回闭包

闭包表现为 trait,这意味着不能直接返回闭包。这段代码尝试直接返回闭包,它并不能编译:

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

编译器给出的错误是:

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
  |
  = 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/lib.rs:2:5: 2:8]`, which implements `Fn(i32) -> i32`
  |
1 | fn returns_closure() -> impl Fn(i32) -> i32 {
  |                         ~~~~~~~~~~~~~~~~~~~

错误又一次指向了 Sized trait!Rust 并不知道需要多少空间来储存闭包。不过我们使用 trait 对象来解决:

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

fn main() {
    returns_closure()(5);
}

前面的错误提示提到可以让 returns_closure 返回值为实现了某个 trait 的具体类型,比如 impl Fn(i32) -> i32

// 指定返回值为实现了 'Fn(i32) -> i32' 这个trait的类型,但是实现了这个trait的类型可以是函数指针,也可以是闭包
fn returns_closure() -> impl Fn(i32) -> i32 {
    let a = 1;
    move |x| x + a
}

// 指定返回值为实现了 'Fn(i32) -> i32' 这个trait的类型,但是实现了这个trait的类型可以是函数指针,也可以是闭包
fn returns_function() -> impl Fn(i32) -> i32 {
    fn function(x: i32) -> i32 {
        x + 1
    }
    function
}

fn main() {
    returns_closure()(6);
    returns_function()(6);
}

returns_closurereturns_function 的返回值类型都是 impl Fn(i32) -> i32因为函数和闭包都实现了 Fn trait,因此这个类型既可以是闭包,也可以是函数指针。 returns_closure 实际返回的是闭包;returns_function 实际返回的是函数指针;像第 16 、17 行那样使用时没有问题的。

如果一个函数明确要求函数参数是函数指针,则虽然 returns_closurereturns_function 的返回值类型都是 impl Fn(i32) -> i32,但将前者的返回值作为函数参数是非法的:

// 指定返回值为实现了 'Fn(i32) -> i32' 这个trait的类型,但是实现了这个trait的类型可以是函数指针,也可以是闭包
fn returns_closure() -> impl Fn(i32) -> i32 {
    let a = 1;
    move |x| x + a
}

fn returns_function() -> impl Fn(i32) -> i32 {
    fn function(x: i32) -> i32 {
        x + 1
    }
    function
}

// 函数指针作为参数,闭包不能作为参数!
fn apply_2(f: fn(i32) -> i32) -> i32 {
    f(2)
}

fn main() {
    apply_2(returns_closure());  //非法!'apply_2'函数参数只能是函数指针!
    apply_2(returns_function());
}