最近几节我们都在聊一些高级特性,这一节来看函数之前没提到过的一些特性:函数指针、高阶函数
函数指针
在讲到闭包时候,我们将闭包可以作为函数参数传递,其实函数也可以作为参数,在参数位置使用fn
来描述函数类型:
fn add(a: i32, b: i32) -> i32 {
a + b
}
// 使用fn类型来描述函数
type Adder = fn(i32, i32) -> i32;
// 描述参数类型
fn run(f1: Adder, f2: Adder) -> i32 {
f1(1, 2) + f2(3, 4)
}
// 将add函数的指针传入run函数
println!("{}", run(add, add));
// 10
函数指针和闭包trait
都可以兼容闭包和普通函数,看下面这个复杂的例子:
// 函数
fn sub(a: i32, b: i32) -> i32 {
a - b
}
// 闭包
let add = |a: i32, b: i32| -> i32 {
a - b
};
// f1是闭包trait,f2是函数指针
fn run(f1: impl Fn(i32, i32) -> i32, f2: fn(i32, i32) -> i32) -> i32 {
f1(1, 2) + f2(3, 4)
}
// f1是函数指针,f2是闭包trait
fn run2(f1: fn(i32, i32) -> i32, f2: impl Fn(i32, i32) -> i32) -> i32 {
f1(1, 2) + f2(3, 4)
}
println!("{}", run(add, sub)); // -2
println!("{}", run2(add, sub)); // -2
下面是一个既可以使用闭包也可以使用命名函数的更具体的例子:
// 使用闭包的情况:
let list_nums = vec![1, 2, 3];
let list_str: Vec<String> = list_nums
.iter()
.map(|n| n.to_string())
.collect();
println!("{:?}", list_str);
// ["1", "2", "3"]
// 使用函数的情况:
let list_str: Vec<String> = list_nums
.iter()
// 这里可以思考一下,ToString是一个trait,这里使用了前面“高级trait”一节中提到的完全限定语法
.map(ToString::to_string)
.collect();
println!("{:?}", list_str);
// ["1", "2", "3"]
枚举也可以作为函数:
#[derive(Debug)]
enum Status {
Value(u32),
Stop,
}
let list_status: Vec<Status> = (1..=3).map(Status::Value)
.collect();
println!("{:?}", list_status);
// [Value(1), Value(2), Value(3)]
这里是一个高阶函数,将函数作为参数,返回一个闭包:
fn add(f: fn(i32) -> i32) -> impl Fn(i32) -> i32 {
// 这里需要move的原因在于借用检查器会认为当add执行完成后f会销毁,
// 而返回的闭包生命周期比较长,这里闭包用使用了add函数中的引用
// 所以需要把f的所有权转移到闭包中用来让借用检查器检查通过
return move |p: i32| {
return f(p) + 1
}
}
fn add_one(x: i32) -> i32 {
x + 1
}
let run = add(add_one);
println!("{}", run(1));
还有种有意思的操作就是使用Box<T>
来返回闭包:
fn return_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
// 将闭包从Box中解引用获取,然后执行
let x = (*return_closure())(1);
println!("{}", x);
// 2
封面图:跟着Tina画美国
关注「码生笔谈」公众号,阅读更多最新章节