Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

高级函数和闭包

Advanced Functions and Closures

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

This section explores some advanced features related to functions and closures, including function pointers and returning closures.

函数指针

Function Pointers

我们已经讨论了如何将闭包传递给函数;你也可以将普通函数传递给函数!当你想要传递一个已经定义的函数而不是定义一个新的闭包时,这种技术非常有用。函数强制转换(coerce)为 fn 类型(小写 f),不要与 Fn 闭包 trait 混淆。fn 类型被称为函数指针 (function pointer)。通过函数指针传递函数允许你将函数作为其他函数的参数。

We’ve talked about how to pass closures to functions; you can also pass regular functions to functions! This technique is useful when you want to pass a function you’ve already defined rather than defining a new closure. Functions coerce to the type fn (with a lowercase f), not to be confused with the Fn closure trait. The fn type is called a function pointer. Passing functions with function pointers will allow you to use functions as arguments to other functions.

指定参数为函数指针的语法与闭包的语法类似,如示例 20-28 所示,我们定义了一个将参数加 1 的函数 add_one。函数 do_twice 接受两个参数:一个指向任何接受 i32 参数并返回 i32 的函数的函数指针,以及一个 i32 值。do_twice 函数调用函数 f 两次,并将 arg 值传递给它,然后将两次函数调用的结果相加。main 函数使用参数 add_one5 调用 do_twice

The syntax for specifying that a parameter is a function pointer is similar to that of closures, as shown in Listing 20-28, where we’ve defined a function add_one that adds 1 to its parameter. The function do_twice takes two parameters: a function pointer to any function that takes an i32 parameter and returns an i32, and one i32 value. The do_twice function calls the function f twice, passing it the arg value, then adds the two function call results together. The main function calls do_twice with the arguments add_one and 5.

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

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

This code prints The answer is: 12. We specify that the parameter f in do_twice is an fn that takes one parameter of type i32 and returns an i32. We can then call f in the body of do_twice. In main, we can pass the function name add_one as the first argument to do_twice.

与闭包不同,fn 是一个类型而不是一个 trait,因此我们直接指定 fn 作为参数类型,而不是声明一个以 Fn trait 之一作为 trait bound 的泛型类型参数。

Unlike closures, fn is a type rather than a trait, so we specify fn as the parameter type directly rather than declaring a generic type parameter with one of the Fn traits as a trait bound.

函数指针实现了所有三个闭包 trait(FnFnMutFnOnce),这意味着你总是可以将函数指针作为参数传递给期望闭包的函数。最好使用泛型类型和其中一个闭包 trait 来编写函数,这样你的函数既可以接受函数也可以接受闭包。

Function pointers implement all three of the closure traits (Fn, FnMut, and FnOnce), meaning you can always pass a function pointer as an argument for a function that expects a closure. It’s best to write functions using a generic type and one of the closure traits so that your functions can accept either functions or closures.

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

That said, one example of where you would want to only accept fn and not closures is when interfacing with external code that doesn’t have closures: C functions can accept functions as arguments, but C doesn’t have closures.

作为一个既可以使用内联定义的闭包又可以使用命名函数的例子,让我们看看标准库中 Iterator trait 提供的 map 方法的使用。为了使用 map 方法将数字向量转换为字符串向量,我们可以使用闭包,如示例 20-29 所示。

As an example of where you could use either a closure defined inline or a named function, let’s look at a use of the map method provided by the Iterator trait in the standard library. To use the map method to turn a vector of numbers into a vector of strings, we could use a closure, as in Listing 20-29.

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 的参数,而不是闭包。示例 20-30 展示了这看起来像什么。

Or we could name a function as the argument to map instead of the closure. Listing 20-30 shows what this would look like.

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 的可用函数。

Note that we must use the fully qualified syntax that we talked about in the “Advanced Traits” section because there are multiple functions available named to_string.

这里,我们使用的是 ToString trait 中定义的 to_string 函数,标准库已经为任何实现 Display 的类型实现了该 trait。

Here, we’re using the to_string function defined in the ToString trait, which the standard library has implemented for any type that implements Display.

回想一下第 6 章“枚举值”部分,我们定义的每个枚举变体的名称也成为了一个初始化函数。我们可以将这些初始化函数作为实现了闭包 trait 的函数指针来使用,这意味着我们可以将初始化函数作为参数指定给接受闭包的方法,如示例 20-31 所示。

Recall from the “Enum Values” section in Chapter 6 that the name of each enum variant that we define also becomes an initializer function. We can use these initializer functions as function pointers that implement the closure traits, which means we can specify the initializer functions as arguments for methods that take closures, as seen in Listing 20-31.

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 实例。有些人喜欢这种风格,有些人则喜欢使用闭包。它们会编译为相同的代码,因此请使用对你来说更清晰的风格。

Here, we create Status::Value instances using each u32 value in the range that map is called on by using the initializer function of Status::Value. Some people prefer this style and some people prefer to use closures. They compile to the same code, so use whichever style is clearer to you.

返回闭包

Returning Closures

闭包是由 trait 表示的,这意味着你不能直接返回闭包。在大多数你可能想要返回 trait 的情况下,你可以转而使用实现该 trait 的具体类型作为函数的返回值。然而,通常不能对闭包这样做,因为它们没有可返回的具体类型;例如,如果闭包从其作用域捕获任何值,则不允许使用函数指针 fn 作为返回类型。

Closures are represented by traits, which means you can’t return closures directly. In most cases where you might want to return a trait, you can instead use the concrete type that implements the trait as the return value of the function. However, you can’t usually do that with closures because they don’t have a concrete type that is returnable; you’re not allowed to use the function pointer fn as a return type if the closure captures any values from its scope, for example.

相反,你通常会使用我们在第 10 章中学到的 impl Trait 语法。你可以使用 FnFnOnceFnMut 返回任何函数类型。例如,示例 20-32 中的代码可以正常编译。

Instead, you will normally use the impl Trait syntax we learned about in Chapter 10. You can return any function type, using Fn, FnOnce, and FnMut. For example, the code in Listing 20-32 will compile just fine.

#![allow(unused)]
fn main() {
fn returns_closure() -> impl Fn(i32) -> i32 {
    |x| x + 1
}
}

但是,正如我们在第 13 章“推断和标注闭包类型”部分指出的,每个闭包也都有其自身独特的类型。如果你需要处理具有相同签名但不同实现的多个函数,则需要为它们使用 trait 对象。考虑如果你编写类似于示例 20-33 所示的代码会发生什么。

However, as we noted in the “Inferring and Annotating Closure Types” section in Chapter 13, each closure is also its own distinct type. If you need to work with multiple functions that have the same signature but different implementations, you will need to use a trait object for them. Consider what happens if you write code like that shown in Listing 20-33.

fn main() {
    let handlers = vec![returns_closure(), returns_initialized_closure(123)];
    for handler in handlers {
        let output = handler(5);
        println!("{output}");
    }
}

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

fn returns_initialized_closure(init: i32) -> impl Fn(i32) -> i32 {
    move |x| x + init
}

这里我们有两个函数 returns_closurereturns_initialized_closure,它们都返回 impl Fn(i32) -> i32。请注意,即使它们实现了相同的类型,它们返回的闭包也是不同的。如果我们尝试编译这个,Rust 会告诉我们它行不通:

Here we have two functions, returns_closure and returns_initialized_closure, which both return impl Fn(i32) -> i32. Notice that the closures that they return are different, even though they implement the same type. If we try to compile this, Rust lets us know that it won’t work:

$ cargo build
   Compiling functions-example v0.1.0 (file:///projects/functions-example)
error[E0308]: mismatched types
  --> src/main.rs:2:44
   |
 2 |     let handlers = vec![returns_closure(), returns_initialized_closure(123)];
   |                                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected opaque type, found a different opaque type
...
 9 | fn returns_closure() -> impl Fn(i32) -> i32 {
   |                         ------------------- the expected opaque type
...
13 | fn returns_initialized_closure(init: i32) -> impl Fn(i32) -> i32 {
   |                                              ------------------- the found opaque type
   |
   = note: expected opaque type `impl Fn(i32) -> i32`
              found opaque type `impl Fn(i32) -> i32`
   = note: distinct uses of `impl Trait` result in different opaque types

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

错误消息告诉我们,每当我们返回 impl Trait 时,Rust 都会创建一个唯一的不透明类型 (opaque type),这是一个我们无法看到 Rust 为我们构造的细节,也无法猜测 Rust 将生成的类型以供我们自己编写。因此,即使这些函数返回实现相同 trait (Fn(i32) -> i32) 的闭包,Rust 为每个函数生成的不透明类型也是截然不同的。(这类似于 Rust 为不同的 async 块生成不同的具体类型,即使它们具有相同的输出类型,正如我们在第 17 章“Pin 类型和 Unpin Trait”中看到的那样。)我们已经多次看到这个问题的解决方案:我们可以使用 trait 对象,如示例 20-34 所示。

The error message tells us that whenever we return an impl Trait, Rust creates a unique opaque type, a type where we cannot see into the details of what Rust constructs for us, nor can we guess the type Rust will generate to write ourselves. So, even though these functions return closures that implement the same trait, Fn(i32) -> i32, the opaque types Rust generates for each are distinct. (This is similar to how Rust produces different concrete types for distinct async blocks even when they have the same output type, as we saw in “The Pin Type and the Unpin Trait” in Chapter 17.) We have seen a solution to this problem a few times now: We can use a trait object, as in Listing 20-34.

fn main() {
    let handlers = vec![returns_closure(), returns_initialized_closure(123)];
    for handler in handlers {
        let output = handler(5);
        println!("{output}");
    }
}

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

fn returns_initialized_closure(init: i32) -> Box<dyn Fn(i32) -> i32> {
    Box::new(move |x| x + init)
}

这段代码可以正常编译。有关 trait 对象的更多信息,请参阅第 18 章中的“使用 trait 对象抽象化共享行为”部分。

This code will compile just fine. For more about trait objects, refer to the section “Using Trait Objects To Abstract over Shared Behavior” in Chapter 18.

接下来,让我们看看宏!

Next, let’s look at macros!