Trait Fn

Fn trait 来自 std::ops 模块,它是 Rust 函数 trait(function traits)家族的一部分,用于表示可以像函数一样调用的类型。具体来说,Fn 表示一个可以重复调用而不修改其状态的闭包或函数指针。它允许类型实现不可变接收器的调用操作,支持泛型编程中的函数式风格。 与 FnMutFnOnce 不同,Fn 是最严格的,要求调用不修改捕获的环境,因此适合并发或重复调用的场景。

Rust 的函数 trait 家族包括 FnFnMutFnOnce,它们是闭包和函数指针的核心抽象,用于描述调用语义。Fn 是其中最受限制的,但也最安全。

1. Fn Trait 简介

1.1 定义和目的

Fn trait 定义在 std::ops::Fn 中,自 Rust 1.0.0 起稳定可用。其语法如下:

#![allow(unused)]
fn main() {
pub trait Fn<Args>: FnMut<Args>
where
    Args: Tuple,
{
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}
}
  • 继承Fn 继承 FnMut,因此实现 Fn 的类型也必须实现 FnMut(进而 FnOnce)。这反映了 trait 的层级:FnOnce 是最宽松的,Fn 是最严格的。
  • 关联类型:无显式关联类型,但通过继承有 Output(从 FnOnce)。
  • 方法call(&self, args: Args) -> Self::Output - 执行调用操作,接收不可变 self 和参数元组。 extern "rust-call" 指定调用约定,支持可变参数。

目的Fn 提供一种抽象函数调用的方式,允许类型(如闭包或函数指针)被当作函数使用,而不修改状态。这在泛型编程中特别有用,可以接受任何可调用的东西作为参数,并重复调用而无需担心副作用。根据官方文档,Fn 适用于需要重复调用且不修改状态的场景,例如并发环境中。 它促进函数式编程,支持高阶函数和闭包的泛型使用。

Fn 的设计目的是与闭包语法集成:Rust 闭包自动实现合适的函数 trait,根据捕获方式(不可变借用 -> Fn;可变借用 -> FnMut;移动 -> FnOnce)。

  • 为什么需要 Fn Rust 的类型系统需要抽象可调用类型。Fn 允许泛型代码接受闭包或函数指针,而无需知道具体实现,支持函数作为一等公民。 例如,在库中定义接受回调的函数,使用 F: Fn(Args) -> Output 边界。

1.2 与相关 Trait 的区别

Fn 是函数 trait 家族的一部分,与 FnMutFnOnce 紧密相关,但各有侧重:

  • FnMut

    • Fn:调用使用不可变 self(&self),不能修改捕获状态;可以重复调用。
    • FnMut:调用使用可变 self(&mut self),可以修改捕获状态;可以重复调用。
    • Fn 继承 FnMut,所以 Fn 类型也可作为 FnMut 使用,但反之不成立。
    • 示例:闭包捕获不可变引用实现 Fn;捕获可变引用实现 FnMut
    • 选择:如果调用不需修改状态,用 Fn 以更严格安全;否则用 FnMut
  • FnOnce

    • Fn:不可变 self,重复调用。
    • FnOnce:消耗 self(self),只能调用一次,可能移动捕获值。
    • Fn 继承 FnMut 继承 FnOnce,所以 Fn 类型也可作为 FnOnce 使用。
    • 示例:闭包移动捕获实现 FnOnce;无移动实现 FnMutFn
    • 选择:如果只需调用一次且可能移动,用 FnOnce;否则用 FnFnMut
  • fn 类型

    • Fn 是 trait;fn 是函数指针类型(如 fn(i32) -> i32)。
    • 函数指针实现 Fn(如果安全),但闭包可能只实现 FnMutFnOnce
    • 示例:let f: fn(i32) -> i32 = add_one; 实现 Fn;闭包可能不。

何时选择?Fn 当需要重复调用且不修改状态时;用 FnMut 当需要修改;用 FnOnce 当只需一次且可能移动。 最佳实践:函数边界用最宽松的(如 FnOnce),以支持更多闭包类型。

2. 自动实现 Fn(Auto-Implemented)

Rust 编译器自动为闭包和函数指针实现合适的函数 trait,根据捕获方式:

  • 无捕获或不可变借用:实现 FnFnMutFnOnce
  • 无需手动实现 Fn;闭包语法自动处理。

2.1 基本示例:闭包

fn main() {
    let add_one = |x: i32| x + 1;  // 实现 Fn(i32) -> i32
    println!("{}", add_one(5));  // 6
}
  • 无捕获闭包实现 Fn

2.2 函数指针

fn square(x: i32) -> i32 { x * x }

fn main() {
    let f: fn(i32) -> i32 = square;  // fn pointer 实现 Fn
    println!("{}", f(5));  // 25
}
  • 安全函数指针实现 Fn

2.3 泛型函数边界

fn call_twice<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
    f(f(x))
}

fn main() {
    let double = |y| y * 2;
    println!("{}", call_twice(double, 5));  // 20
}
  • F: Fn 确保可重复调用。

3. 手动实现 Fn

手动实现 Fn 罕见,通常用于自定义可调用类型。需实现 call,并继承 FnMutFnOnce

3.1 手动示例

use std::ops::{Fn, FnMut, FnOnce};

struct Adder(i32);

impl FnOnce<(i32,)> for Adder {
    type Output = i32;
    extern "rust-call" fn call_once(self, args: (i32,)) -> i32 {
        self.0 + args.0
    }
}

impl FnMut<(i32,)> for Adder {
    extern "rust-call" fn call_mut(&mut self, args: (i32,)) -> i32 {
        self.0 + args.0
    }
}

impl Fn<(i32,)> for Adder {
    extern "rust-call" fn call(&self, args: (i32,)) -> i32 {
        self.0 + args.0
    }
}

fn main() {
    let adder = Adder(10);
    println!("{}", adder(5));  // 15
}
  • 自定义类型实现 Fn

4. 高级主题

4.1 函数 trait 层级

  • FnOnce:最宽松,只需调用一次。
  • FnMut:继承 FnOnce,可重复,可修改。
  • Fn:继承 FnMut,可重复,不可修改。
  • 使用最宽边界以最大兼容性。

4.2 与 Trait 对象

trait MyFn: Fn(i32) -> i32 {}

impl MyFn for fn(i32) -> i32 {}

fn main() {
    let f: Box<dyn MyFn> = Box::new(|x| x + 1);
    println!("{}", f(5));  // 6
}
  • 支持动态函数。

4.3 Crate fn_traits

使用 crate 如 fn_traits 在稳定 Rust 中手动实现函数 trait。

5. 常见用例

  • 高阶函数:接受闭包作为参数。
  • 回调:事件处理。
  • 函数指针:存储函数。
  • 并发:不可变闭包在多线程。
  • 泛型库:如 iterators 的 map。

6. 最佳实践

  • 选择合适 trait:用 Fn 以严格安全。
  • 边界最宽:函数参数用 FnOnce 以兼容。
  • 闭包语法:依赖自动实现。
  • 文档:说明边界原因。
  • 测试:验证调用不修改状态。
  • 性能Fn 无开销。

7. 常见陷阱和错误

  • 捕获方式:错误捕获导致错 trait;用 & 或 mut。
  • 边界太严Fn 拒绝 FnMut 闭包;用 FnMut
  • 函数指针 vs 闭包:函数指针实现 Fn,但闭包可能不。
  • Trait 对象大小:dyn Fn 需要 Box 或 &。
  • 稳定限制:手动实现需 nightly 或 crate。