[!|center] 普若哥们儿
高级函数与闭包
函数指针
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_one 和 5 作为参数调用 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(Fn、FnMut 和 FnOnce),所以总是可以在调用闭包作为参数的函数时传递函数指针作为参数。比如:
// 定义一个函数,可以接受一个由 `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_closure 和 returns_function 的返回值类型都是 impl Fn(i32) -> i32,因为函数和闭包都实现了 Fn trait,因此这个类型既可以是闭包,也可以是函数指针。
returns_closure 实际返回的是闭包;returns_function 实际返回的是函数指针;像第 16 、17 行那样使用时没有问题的。
如果一个函数明确要求函数参数是函数指针,则虽然 returns_closure 和 returns_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());
}