【Rust自学】19.3. 高级函数和闭包
喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
19.3.1. 函数指针(function pointer)
我们之前讲过如何把闭包传给函数,而实际上我们还可以把函数传递给函数。
在传递过程中,函数会被强制转换为fn
类型,也就是函数指针(function pointer)。
看个例子:
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}");
}
do_twice
的第一个参数f
是fn
类型的,也就是一个函数指针。它要求作为参数的这个函数的参数是i32
,返回值也是i32
。在函数体里两次调用了f
。
输出:
The answer is 12
函数指针与闭包的不同
闭包最起码实现了Fn
、FnOnce
和FnMut
这三个trait之一,而函数指针fn
是一个类型而不是trait。我们可以直接指定fn
为参数类型,不用生命一个以Fn
trait为约束的泛型参数。
函数指针实现了全部3种闭包trait,也就是Fn
、FnOnce
和FnMut
这三个trait。所以总是可以把函数指针用作参数传递给一个接收闭包的函数。正是因为这个原因,我们倾向于搭配闭包trait的泛型来编写函数,这样可以同时接收闭包和普通函数。
而在某些情况下,我们可能想要接收fn
类型而不是闭包,比如说与外部不支持闭包的代码(C函数)交互,我们该怎么写呢?
看例子:
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();
// 分行写只是为了好看,不是必要
}
list_of_numbers
这个Vector
里的元素是i32
类型,而后文我们想要把list_of_numbers
的元素转换为String
类型赋给list_of_strings
。具体做法就是:
- 先使用
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();
}
不同之处就在于.map(ToString::to_string)
,这里是直接把to_string
这个函数传进去了。这样写跟之前那么写的效果是一样的。顺带一提,ToString::to_string
使用了前一篇文章所说的完全限定语法的知识。
我们看看map
方法的定义:
fn map<B, F>(self, f:F) -> Map<Self, F>
where
Self: Sized
F: FnMut(self::Item) -> B
map
要求f
实现FnMut
这个trait,而闭包和函数指针都满足这一条件所以可以往里面传。
再看一个例子:
fn main(){
enum Status {
Value(u32),
Stop,
}
let list_of_statuses: Vec<Status> = (0u32..20)
.map(Status::Value)
.collect();
}
注意看map
方法的参数,我们使用Status::Value
的初始化函数调用map
范围内的每个u32
值来创建Status::Value
实例。
有人可能会问了Status::Value
不是一个枚举类型变体吗?怎么又变成了函数呢?这是因为在Rust中这样的构造器被实现为了函数,它会接收一个参数并返回一个新的实例。也就是说:
let v = Status::value(3);
这是我随便举的例子,这里面的v
被初始化了,而Status::value(3)
就可以被看作是一个构造器,3是构造器的参数。而构造器又被实现为了函数,所以可以把它理解为函数,而3是其参数。
所以我们可以把这样的构造器也作为实现了闭包trait的函数指针来进行使用。
19.3.2. 返回闭包
闭包使用trait进行表达,无法在函数中直接返回一个闭包,可以将一个实现了该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
|
help: consider returning an `impl Trait` instead of a `dyn Trait`
|
1 | fn returns_closure() -> impl Fn(i32) -> i32 {
| ~~~~
help: alternatively, box the return type, and wrap all of the returned values in `Box::new`
|
1 ~ fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
2 ~ Box::new(|x| x + 1)
|
For more information about this error, try `rustc --explain E0746`.
error: could not compile `functions-example` (lib) due to 1 previous error
Rust不知道需要多少空间来存储闭包,所以会报错。
还记得我们还在哪里遇到过Rust不知道需要多少空间来存储这个错误吗?没错,在链表时也遇到过,当时的做法是使用Box<T>
包裹链表,这里也可以这么写:
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
由于返回值是一个指针,所以这个时候返回类型就有固定的大小(usize
)了。