函数指针 (function pointer ):目的是让函数作为另一个函数的参数。
语法:需要函数作为参数时,类型标注使用 f: fn(args..) -> ReturnType
,即类型写成 函数签名的形式,而签名中的函数名使用 fn
来表明这是一个函数指针。
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
}
注意:
fn
是一个类型(函数指针)而不是一个 trait,Fn
是一个 trait。fn
实现了所有三个闭包 trait(Fn
、FnMut
和FnOnce
),所以总是可以在调用期望闭包的函数时传递函数指针作为参数。- 所以
fn
函数指针 与Fn
trait bound +泛型 都能让函数作为参数:下面这个例子展示这两个方法功能很相似struct FooGenerics<T>
where T: Fn(u32) -> u32
{
calculation: T,
value: u32,
}
type Func = fn(u32) -> u32;
struct Foo {
calculation: Func,
value: u32,
}
fn addone(x: u32) -> u32 { x + 1 }
fn main() {
// 传入闭包
let f1 = FooGenerics { calculation: |x| x + 1, value: 0 };
println!("{}", (f1.calculation)(f1.value));
let f2 = Foo { calculation: |x| x + 1, value: 0 };
println!("{}", (f2.calculation)(f2.value));
// 传入函数
let f1 = FooGenerics { calculation: addone, value: 0 };
println!("{}", (f1.calculation)(f1.value));
let f2 = Foo { calculation: addone, value: 0 };
println!("{}", (f2.calculation)(f2.value));
}
但是别忘了// fn 函数指针
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
f(arg) + f(arg)
}
// Fn trait bound + 泛型
// fn do_twice(f: impl Fn(i32) -> i32, arg: i32) -> i32 { f(arg) + f(arg) }
// 上下等价
fn do_twice<T>(f: T, arg: i32) -> i32
where T: Fn(i32) -> i32 {
f(arg) + f(arg)
}
Fn
、FnMut
和FnOnce
闭包 trait 的 “在捕获环境上的区别”,普通函数可不具备捕获上下文的功能。
此外,fn
与Fn
+泛型 在函数返回值时有差异:
fn
作为指针类型,大小是固定的,所以可以通过编译- 而
Fn
+泛型 属于动态类型,必须在它前面使用 指针类型+dyn关键字才能返回 - 例子见 “返回闭包”
- 一个只希望接受
fn
而不接受闭包的情况的例子是 与不存在闭包的外部代码交互时:C 语言的函数可以接受函数作为参数,但 C 语言没有闭包。 看一个有点“奇怪”但语法又十分简洁的例子:元组结构体 或者 元组结构体枚举成员 的初始化函数作为函数指针
#![allow(unused)]
#[derive(Debug)]
enum Status {
Value(u32), // 这看起来就像函数调用 fn(u32),而这确实被实现为返回由参数构造的实例的函数
Stop,
}
fn main() {
let list_of_statuses: Vec<Status> = (0u32..5).map(Status::Value).collect();
println!("{:?}", list_of_statuses);
// [Value(0), Value(1), Value(2), Value(3), Value(4)]
}
通常我们实例化枚举体时使用这样的语句
let v = Status::Value(0);
,Status::Value
可以被当作关联函数用来调用,它的作用在于初始化(实例化)。这个例子反映我们无需对Status
定义有关于Iterator
trait 或者collect
方法的相关代码就能利用 Rust 强大的类型系统,做到看似困难的事情。最后看看
map
方法的例子,传入内联定义的闭包或者命名函数完成同样的事情,同时也学习一下ToString
trait 的应用:fn main() {
let list_of_numbers = vec![1, 2, 3];
let use_closure: Vec<String> =
list_of_numbers.iter().map(|i| i.to_string()).collect();
let use_named_function: Vec<String> =
list_of_numbers.iter().map(ToString::to_string).collect();
println!("{:?}\n{:?}", use_closure, use_named_function);
}
// 打印结果:
// ["1", "2", "3"]
// ["1", "2", "3"]
这里使用了定义于
ToString
trait 的to_string
函数,标准库为所有实现了Display
的类型实现了这个 trait。
一些人倾向于函数风格,一些人喜欢闭包。这两种形式最终都会产生同样的代码,所以请使用对你来说更明白的形式吧。返回闭包
闭包表现为 trait(具体来说是
Fn
系的三大 trait),这意味着不能直接返回闭包。
对于大部分需要返回 trait 的情况,可以使用实现了期望返回的 trait 的具体类型来替代函数的返回值。
但是这不能用于闭包,因为他们没有一个可返回的具体类型。fn returns_closure() -> Fn(i32) -> i32 {
|x| x + 1
}
// 得到以下报错:
error[E0277]: the trait bound `std::ops::Fn(i32) -> i32 + 'static:
std::marker::Sized` is not satisfied
-->
|
1 | fn returns_closure() -> Fn(i32) -> i32 {
| ^^^^^^^^^^^^^^ `std::ops::Fn(i32) -> i32 + 'static`
does not have a constant size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for
`std::ops::Fn(i32) -> i32 + 'static`
= note: the return type of a function must have a statically known size
错误指向了
Sized
trait。解决方式有两种:使用 trait 对象:在它前面使用 指针类型+dyn关键字
- 使用函数指针
fn
:因为指针大小是固定的,具有Size
traitfn returns_closure_use_trait_object() -> Box<dyn Fn(i32) -> i32> { Box::new(|x| x + 1) }
fn returns_closure_use_function_pointer() -> fn(i32) -> i32 { |x| x + 1 }
fn main() {
let func = returns_closure_use_trait_object();
println!("{}", func(1));
let func = returns_closure_use_function_pointer();
println!("{}", func(1));
}