函数指针 (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函数指针 与Fntrait 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) -> i32where 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定义有关于Iteratortrait 或者collect方法的相关代码就能利用 Rust 强大的类型系统,做到看似困难的事情。最后看看
map方法的例子,传入内联定义的闭包或者命名函数完成同样的事情,同时也学习一下ToStringtrait 的应用: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"]
这里使用了定义于
ToStringtrait 的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
错误指向了
Sizedtrait。解决方式有两种:使用 trait 对象:在它前面使用 指针类型+dyn关键字
- 使用函数指针
fn:因为指针大小是固定的,具有Sizetraitfn 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));}
