当前位置: 首页 > news >正文

Rust 练习册 5:Fn、FnMut 和 FnOnce trait

在 Rust 中,闭包是实现匿名函数的一种方式,但闭包并不仅仅是一种函数。Rust 有三个不同的 trait 来表示闭包的不同行为:FnFnMutFnOnce。理解这些 trait 的区别对于编写高效和正确的 Rust 代码至关重要。

什么是 Fn trait 系列?

Rust 中的三个 Fn trait 定义了闭包如何与捕获的变量进行交互:

  1. Fn:不可变地借用捕获的变量
  2. FnMut:可变地借用捕获的变量
  3. FnOnce:获取捕获变量的所有权

项目中的示例代码

让我们先看看项目中的示例代码:

fn main() {let c1 = || "c1";let c2 = || "c2";let v = [c1, c2];let mut i = "c3";let c3 = || i;// let v = [c1, c2, c3];let c4: || {i.to_owned() + "c4"};// let v = [c1,c2,c4];
}

在这个示例中,我们定义了几个闭包:

  • c1c2 是不捕获任何变量的闭包
  • c3 捕获了变量 i 的不可变引用
  • c4 捕获了变量 i 并对其进行操作

Fn trait 的层次结构

这三个 trait 之间存在层次关系:

// Fn trait 可以调用多次,不改变捕获的变量
trait Fn<Args> {type Output;fn call(&self, args: Args) -> Self::Output;
}// FnMut trait 可以调用多次,可能改变捕获的变量
trait FnMut<Args> {type Output;fn call_mut(&mut self, args: Args) -> Self::Output;
}// FnOnce trait 只能调用一次,可能消耗捕获的变量
trait FnOnce<Args> {type Output;fn call_once(self, args: Args) -> Self::Output;
}

Fn trait 的实际应用

Fn - 不可变借用

fn fn_example() {let x = 5;let closure = |y| x + y; // 闭包不可变地借用 xprintln!("Result: {}", closure(3)); // 8println!("x is still: {}", x); // 5,x 没有被改变// 可以多次调用println!("Result: {}", closure(2)); // 7
}

FnMut - 可变借用

fn fn_mut_example() {let mut count = 0;let mut closure = |x| {count += 1; // 闭包可变地借用 countx + count};println!("Result: {}", closure(5)); // 6 (5 + 1)println!("Result: {}", closure(5)); // 7 (5 + 2)println!("Result: {}", closure(5)); // 8 (5 + 3)
}

FnOnce - 获取所有权

fn fn_once_example() {let s = "Hello".to_string();let closure = move || {println!("{}", s); // 闭包获取 s 的所有权};closure(); // 可以调用一次// closure(); // 这会编译错误,因为 s 的所有权已经被移动
}

闭包的自动 trait 实现

Rust 编译器会根据闭包如何使用捕获的变量自动为其实现相应的 trait:

fn closure_trait_implementation() {let x = 5;// 不访问捕获的变量 -> 实现 Fnlet c1 = || println!("Hello");// 不可变访问捕获的变量 -> 实现 Fnlet c2 = || println!("x is {}", x);// 可变访问捕获的变量 -> 实现 FnMutlet mut y = 10;let mut c3 = || {y += 1;println!("y is {}", y);};// 消费捕获的变量 -> 实现 FnOncelet s = "Hello".to_string();let c4 = move || {println!("{}", s); // s 的所有权被移动到闭包中};
}

函数参数中的 Fn trait

我们可以使用这些 trait 作为函数参数的约束:

// 接受 Fn trait 的函数
fn call_with_one<F>(func: F) -> i32
whereF: Fn(i32) -> i32,
{func(1)
}// 接受 FnMut trait 的函数
fn call_mut_with_counter<F>(mut func: F) 
whereF: FnMut(i32),
{for i in 0..3 {func(i);}
}// 接受 FnOnce trait 的函数
fn consume_closure<F>(func: F) 
whereF: FnOnce(),
{func();
}fn function_parameters_example() {let x = 5;let add_x = |y| x + y;println!("Result: {}", call_with_one(add_x)); // 6let mut count = 0;let mut increment = || {count += 1;println!("Count: {}", count);};call_mut_with_counter(increment);let s = "Hello".to_string();let print_string = move || println!("{}", s);consume_closure(print_string);
}

闭包 trait 的关系

重要的是要理解这三个 trait 之间的关系:

fn trait_relationships() {// 所有实现 Fn 的闭包也实现 FnMut 和 FnOnce// 所有实现 FnMut 的闭包也实现 FnOnce// 但反之不成立let x = 5;// 这个闭包实现 Fn、FnMut 和 FnOncelet c1 = |y| x + y;// 这个闭包只实现 FnMut 和 FnOncelet mut z = 10;let mut c2 = || {z += 1;z};// 这个闭包只实现 FnOncelet s = "Hello".to_string();let c3 = move || {println!("{}", s);};// 使用示例fn accept_fn<F: Fn()>(f: F) {f();f(); // 可以多次调用}fn accept_fn_mut<F: FnMut()>(mut f: F) {f();f(); // 可以多次调用}fn accept_fn_once<F: FnOnce()>(f: F) {f();// f(); // 不能再次调用}// accept_fn(c1); // 正确// accept_fn_mut(c1); // 也正确,因为 Fn 是 FnMut 的子 trait// accept_fn_once(c1); // 也正确,因为 Fn 是 FnOnce 的子 trait// accept_fn(c2); // 错误!c2 不实现 Fn// accept_fn_mut(c2); // 正确// accept_fn_once(c2); // 也正确// accept_fn(c3); // 错误!c3 不实现 Fn// accept_fn_mut(c3); // 错误!c3 不实现 FnMut// accept_fn_once(c3); // 正确
}

实际应用场景

迭代器适配器

fn iterator_adapters() {let numbers = vec![1, 2, 3, 4, 5];// map 接受 FnMutlet doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();// filter 接受 Fnlet evens: Vec<&i32> = numbers.iter().filter(|&x| x % 2 == 0).collect();// fold 接受 FnMutlet sum = numbers.iter().fold(0, |acc, &x| acc + x);println!("Doubled: {:?}", doubled);println!("Evens: {:?}", evens);println!("Sum: {}", sum);
}

线程处理

use std::thread;fn threading_example() {let s = "Hello".to_string();// thread::spawn 需要 FnOnce,因为闭包会被移动到新线程let handle = thread::spawn(move || {println!("{}", s);});handle.join().unwrap();
}

错误处理

fn error_handling_example() {let numbers = vec![1, 2, 3, 4, 5];// unwrap_or_else 接受 FnOncelet result = numbers.get(10).copied().unwrap_or_else(|| {println!("Index out of bounds, using default value");0});println!("Result: {}", result);
}

最佳实践

1. 选择合适的 trait 约束

// 好的做法:使用最宽松的约束
fn apply_function<T, F>(value: T, func: F) -> T
whereF: Fn(T) -> T, // 如果不需要可变性,不要使用 FnMut
{func(value)
}// 避免:过度约束
fn apply_function_over_constrained<T, F>(value: T, mut func: F) -> T
whereF: FnMut(T) -> T, // 如果不需要可变性,这是过度约束
{func(value)
}

2. 理解 move 关键字

fn move_keyword_example() {let s = "Hello".to_string();// 不使用 move,闭包借用 slet closure_borrow = || println!("{}", s);// 使用 move,闭包获取 s 的所有权let closure_move = move || println!("{}", s);closure_borrow();closure_borrow(); // 可以多次调用closure_move();// closure_move(); // 编译错误!s 的所有权已被移动
}

总结

Fn trait 系列是 Rust 中处理闭包的重要概念:

  1. Fn:最严格的 trait,只允许不可变借用捕获的变量
  2. FnMut:允许可变借用捕获的变量
  3. FnOnce:最宽松的 trait,允许获取捕获变量的所有权

关键要点:

  • 三个 trait 之间存在层次关系:Fn ⊆ FnMut ⊆ FnOnce
  • Rust 编译器会根据闭包如何使用捕获的变量自动实现相应的 trait
  • 在函数参数中使用合适的 trait 约束
  • 理解 move 关键字对闭包行为的影响

通过合理使用这些 trait,我们可以编写出更加灵活和高效的 Rust 代码。

http://www.dtcms.com/a/574709.html

相关文章:

  • 哈尔滨cms模板建站wordpress 支持小工具
  • 上海公司查询网站网站改版 新闻
  • 电阻发热的底层逻辑
  • 虚拟机原理
  • 2003访问网站提示输入用户名密码wordpress右键插件
  • 营销网站建设的目的推广你公司网站
  • 如何建设音乐网站如何注册一个平台
  • 网址导航浏览器下载苏州seo优化外包公司
  • DVL多普勒速度计原理与嵌入式实现
  • vs怎么建手机网站网站开发开题报告范文2019
  • 迅为RK3576开发板编译环境Ubuntu20.04编译配置-修改物理内存
  • 岗贝路网站建设建设网站公司电话号码
  • 国内做网站多少钱特定ip段访问网站代码
  • Android控制三方音乐应用播放方案(实测可用)
  • 泰国金木棉做网站网站适合新手做的网站项目
  • 网站编辑面试问题和答案小程序源码分享
  • 怎么做pc端移动网站北京官方网站网
  • 文山网站建设兼职c 网站开发 书
  • 网站开发排行微网站建设方案财政
  • 为什么没人做物流网站郑州做网站哪个平台好
  • 26.序列模型
  • 安阳网站建设商祺wordpress qq登入设置
  • AtCoder Beginner Contest 430(ABCDEF)
  • 公关做的好的网站网络科技公司一般是做什么的
  • 高端制作网站服务用织梦做外文网站
  • postgres15 flink cdc同步测试
  • 做网站策划案自己做的腾讯充值网站
  • 网站建设经理岗位职责soho网站建设教程
  • 【数据集+完整源码】马数据集,马行为状态识别数据集 3006 张,yolov8目标检测牧场草原马识别算法实战训推教程
  • 网站开发公司云鲸互创实惠同企网站建设做网站