函数指针function pointer ):目的是让函数作为另一个函数的参数。
语法:需要函数作为参数时,类型标注使用 f: fn(args..) -> ReturnType,即类型写成 函数签名的形式,而签名中的函数名使用 fn 来表明这是一个函数指针。

  1. fn add_one(x: i32) -> i32 {
  2. x + 1
  3. }
  4. fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
  5. f(arg) + f(arg)
  6. }
  7. fn main() {
  8. let answer = do_twice(add_one, 5);
  9. println!("The answer is: {}", answer);
  10. // The answer is: 12
  11. }

注意:

  1. fn 是一个类型(函数指针)而不是一个 trait,Fn 是一个 trait。
  2. fn 实现了所有三个闭包 trait(FnFnMutFnOnce),所以总是可以在调用期望闭包的函数时传递函数指针作为参数。
  3. 所以 fn 函数指针 与 Fn trait bound +泛型 都能让函数作为参数:下面这个例子展示这两个方法功能很相似
    1. struct FooGenerics<T>
    2. where T: Fn(u32) -> u32
    3. {
    4. calculation: T,
    5. value: u32,
    6. }
    7. type Func = fn(u32) -> u32;
    8. struct Foo {
    9. calculation: Func,
    10. value: u32,
    11. }
    12. fn addone(x: u32) -> u32 { x + 1 }
    13. fn main() {
    14. // 传入闭包
    15. let f1 = FooGenerics { calculation: |x| x + 1, value: 0 };
    16. println!("{}", (f1.calculation)(f1.value));
    17. let f2 = Foo { calculation: |x| x + 1, value: 0 };
    18. println!("{}", (f2.calculation)(f2.value));
    19. // 传入函数
    20. let f1 = FooGenerics { calculation: addone, value: 0 };
    21. println!("{}", (f1.calculation)(f1.value));
    22. let f2 = Foo { calculation: addone, value: 0 };
    23. println!("{}", (f2.calculation)(f2.value));
    24. }
    1. // fn 函数指针
    2. fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    3. f(arg) + f(arg)
    4. }
    5. // Fn trait bound + 泛型
    6. // fn do_twice(f: impl Fn(i32) -> i32, arg: i32) -> i32 { f(arg) + f(arg) }
    7. // 上下等价
    8. fn do_twice<T>(f: T, arg: i32) -> i32
    9. where T: Fn(i32) -> i32 {
    10. f(arg) + f(arg)
    11. }
    但是别忘了 FnFnMutFnOnce 闭包 trait 的 “在捕获环境上的区别”,普通函数可不具备捕获上下文的功能。
    此外,fnFn +泛型 在函数返回值时有差异:
  • fn 作为指针类型,大小是固定的,所以可以通过编译
  • Fn +泛型 属于动态类型,必须在它前面使用 指针类型+dyn关键字才能返回
  • 例子见 “返回闭包”
  1. 一个只希望接受 fn 而不接受闭包的情况的例子是 与不存在闭包的外部代码交互时:C 语言的函数可以接受函数作为参数,但 C 语言没有闭包。
  2. 看一个有点“奇怪”但语法又十分简洁的例子:元组结构体 或者 元组结构体枚举成员 的初始化函数作为函数指针

    1. #![allow(unused)]
    2. #[derive(Debug)]
    3. enum Status {
    4. Value(u32), // 这看起来就像函数调用 fn(u32),而这确实被实现为返回由参数构造的实例的函数
    5. Stop,
    6. }
    7. fn main() {
    8. let list_of_statuses: Vec<Status> = (0u32..5).map(Status::Value).collect();
    9. println!("{:?}", list_of_statuses);
    10. // [Value(0), Value(1), Value(2), Value(3), Value(4)]
    11. }

    通常我们实例化枚举体时使用这样的语句 let v = Status::Value(0);Status::Value 可以被当作关联函数用来调用,它的作用在于初始化(实例化)。这个例子反映我们无需对 Status 定义有关于 Iterator trait 或者 collect 方法的相关代码就能利用 Rust 强大的类型系统,做到看似困难的事情。

  3. 最后看看 map 方法的例子,传入内联定义的闭包或者命名函数完成同样的事情,同时也学习一下 ToString trait 的应用:

    1. fn main() {
    2. let list_of_numbers = vec![1, 2, 3];
    3. let use_closure: Vec<String> =
    4. list_of_numbers.iter().map(|i| i.to_string()).collect();
    5. let use_named_function: Vec<String> =
    6. list_of_numbers.iter().map(ToString::to_string).collect();
    7. println!("{:?}\n{:?}", use_closure, use_named_function);
    8. }
    9. // 打印结果:
    10. // ["1", "2", "3"]
    11. // ["1", "2", "3"]

    这里使用了定义于 ToString trait 的 to_string 函数,标准库为所有实现了 Display 的类型实现了这个 trait。
    一些人倾向于函数风格,一些人喜欢闭包。这两种形式最终都会产生同样的代码,所以请使用对你来说更明白的形式吧。

    返回闭包

    闭包表现为 trait(具体来说是 Fn 系的三大 trait),这意味着不能直接返回闭包。
    对于大部分需要返回 trait 的情况,可以使用实现了期望返回的 trait 的具体类型来替代函数的返回值。
    但是这不能用于闭包,因为他们没有一个可返回的具体类型。

    1. fn returns_closure() -> Fn(i32) -> i32 {
    2. |x| x + 1
    3. }
    4. // 得到以下报错:
    5. error[E0277]: the trait bound `std::ops::Fn(i32) -> i32 + 'static:
    6. std::marker::Sized` is not satisfied
    7. -->
    8. |
    9. 1 | fn returns_closure() -> Fn(i32) -> i32 {
    10. | ^^^^^^^^^^^^^^ `std::ops::Fn(i32) -> i32 + 'static`
    11. does not have a constant size known at compile-time
    12. |
    13. = help: the trait `std::marker::Sized` is not implemented for
    14. `std::ops::Fn(i32) -> i32 + 'static`
    15. = note: the return type of a function must have a statically known size

    错误指向了 Sized trait。解决方式有两种:

  4. 使用 trait 对象:在它前面使用 指针类型+dyn关键字

  5. 使用函数指针 fn:因为指针大小是固定的,具有 Size trait
    1. fn returns_closure_use_trait_object() -> Box<dyn Fn(i32) -> i32> { Box::new(|x| x + 1) }
    2. fn returns_closure_use_function_pointer() -> fn(i32) -> i32 { |x| x + 1 }
    3. fn main() {
    4. let func = returns_closure_use_trait_object();
    5. println!("{}", func(1));
    6. let func = returns_closure_use_function_pointer();
    7. println!("{}", func(1));
    8. }