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

恋爱脑学Rust之闭包三Traits:Fn,FnOnce,FnMut

在这里插入图片描述

在Rust中,FnOnce、FnMut和Fn是三个用于表示闭包(closure)类型的trait。闭包是一种特殊的函数,它可以捕获其环境变量,即在其定义时所处的作用域中的变量。以下是关于这三个trait的详细介绍:


1. FnOnce:一生一次的承诺

理解FnOnce 就像在爱情中那个“一诺千金”的承诺。它只能被调用一次,付出了就没有回头路。我们在 Rust 中通常会用 FnOnce 处理那些需要“独占”资源的闭包,因为它会拿走所有权。

实际应用场景

比如你设计一个“任务系统”,只允许任务被执行一次。这时候可以用 FnOnce 确保执行后资源不再使用。

代码示例
fn execute_task<F>(task: F)
where
    F: FnOnce() -> String,
{
    println!("任务开始:{}", task());
    println!("任务完成!");
}

fn main() {
    let farewell = String::from("离别时的承诺——我会一直记得你。");
    // `FnOnce` 闭包只能调用一次
    execute_task(move || farewell)
}

在这个例子中,execute_task 函数接受一个 FnOnce 闭包,并调用一次。因为闭包拿走了 farewell 的所有权,所以调用后就不再拥有它了。这种设计在需要“唯一性”的任务或资源独占场景中特别有用。

唯一性任务或资源独占是举个例子

在需要资源独占和“一次性任务”的设计中,通常是因为某些操作会消耗资源、改变状态或生成副作用,这些任务在执行后不应被再次调用。典型场景包括文件的独占写入、数据库的唯一记录生成、网络连接的关闭等。在这些场景中使用 FnOnce 设计模式可以确保资源被安全地独占并只被使用一次。

** 实际场景:一次性文件写入 **

举个具体例子,假设我们有一个日志系统,该系统在某些操作完成后会生成一次性的报告,并将它写入文件。由于这份报告只能生成一次,并且会消耗一定资源(比如文件句柄、内存等),因此我们可以使用 FnOnce 来确保只调用一次。

use std::fs::File;
use std::io::{self, Write};

/// 写入报告的函数,使用 FnOnce 确保资源独占
fn write_report<F>(generate_report: F) -> io::Result<()>
where
    F: FnOnce() -> String,
{
    let mut file = File::create("report.txt")?;
    let report_content = generate_report(); // 生成并获取报告内容
    writeln!(file, "{}", report_content)?;
    Ok(())
}

fn main() {
    // 使用 `FnOnce` 闭包来生成报告内容
    let report_closure = || {
        String::from("系统性能报告:\nCPU 使用率:45%\n内存使用率:60%\n...")
    };
    
    // 执行一次性写入
    if let Err(e) = write_report(report_closure) {
        eprintln!("报告写入失败: {}", e);
    } else {
        println!("报告已成功写入 report.txt。");
    }

    // 再次调用 `report_closure` 会导致编译错误
    // error[E0382]: use of moved value: `report_closure`
    // 这是因为 `report_closure` 的所有权已经在 `write_report` 中被消耗
    let another_report = report_closure(); // ❌ 错误:`report_closure` 只能调用一次
    println!("再次生成的报告: {}", another_report);
}

注释与解释

  1. 首次调用 report_closurereport_closurewrite_report 函数内部被调用,这是 FnOnce 闭包的首次使用,也是唯一一次符合所有权规则的调用。
  2. 再次调用 report_closure:尝试再次调用 report_closure 时,Rust 编译器会产生错误 error[E0382]: use of moved value,因为 report_closure 的所有权在 write_report 中被完全消耗,因此无法再次使用。

关键点

  • 一次性调用保证:Rust 强制执行 FnOnce 闭包的“一次性调用”规则,避免了资源重复使用,确保了一次性任务的安全性和数据正确性。
  • 防止数据不一致:在一些操作中(如文件生成、系统操作等),数据只能生成一次,防止重复调用的错误帮助减少潜在的并发问题或数据不一致问题。

2. FnMut:随着时光的推移而改变

理解FnMut 代表着随着时光的推移而不断变化的爱。我们会随着岁月逐渐改变,但初心依旧。在 Rust 中,FnMut 闭包可以多次调用,每次调用都允许内部状态发生变化,比如记录次数、更新变量等。

实际应用场景

你可以使用 FnMut 来创建一个计数器,记录事件的发生次数。比如在按钮被按下时,每次记录点击次数,这就是 FnMut 的用武之地。

代码示例
fn count_visits<F>(mut visit: F)
where
    F: FnMut() -> String,
{
    for _ in 0..3 {
        println!("{}", visit());
    }
}

fn main() {
    let mut visits = 0;
    let mut track_visit = move || {
        visits += 1;
        format!("这是第{}次见到你了!", visits)
    };
    count_visits(track_visit);
}

在这个例子中,每次调用 track_visit 都会更新 visits 的值。因为我们需要修改闭包内部的状态(计数次数),所以选择了 FnMut,它在被多次调用时仍能更新变量。


3. Fn:恒久不变的守护

理解Fn 是那个始终不变的承诺,日复一日的陪伴,永远如一。Fn 闭包不会改变内部状态,能够多次调用并始终如初。在 Rust 中,Fn 是适用于无状态或线程安全的并发计算,因为它不会持有可变数据。

实际应用场景

假如你需要创建一个多次使用的计算函数,比如返回固定信息的服务。Fn 就很适合,既不会占用资源,又保证线程安全。

代码示例
fn repeat_message<F>(message: F)
where
    F: Fn() -> String,
{
    for _ in 0..3 {
        println!("{}", message());
    }
}

fn main() {
    let phrase = String::from("我会一直陪伴你。");
    let say_always = || phrase.clone(); // 使用 `Fn` 闭包,不会改变任何状态
    repeat_message(say_always);
}

在这里,每次调用 say_always 都会输出同样的内容,因为闭包没有持有任何可变状态。在并发场景中,Fn 也可以安全地在线程中共享,适用于那些需要持续执行同一任务的情况。


总结

  • FnOnce:一次性的承诺,适合需要独占资源的场景;
  • FnMut:会随着时间变化的承诺,适合多次调用且需要更新状态的情况;
  • Fn:恒久不变的陪伴,适合需要线程安全、状态不变的计算或服务。

这些不同的承诺类型让我们在 Rust 中设计灵活又安全的闭包调用方式,从而更好地控制资源和状态。


http://www.kler.cn/a/373208.html

相关文章:

  • 【flask】 前后端通信方式 原生js的ajax,总结
  • pgsql表分区和表分片设计
  • Oracle数据泵(expdp)导入导出数据
  • vue打包项目通过docker部署nginx服务
  • 关于深度学习方向学习的一些建议
  • vue3中,实现列表无缝滚动,vue3-seamless-scroll
  • [Mysql] 介绍一下PROCEDURE、TRIGGERS和EVENTS
  • AdaBoost与前向分步算法
  • 使用openssl生成自签名证书(多域名)用于https的ssl验证
  • 【Java SE】变量与常量
  • JVM机制
  • 视频美颜平台的搭建指南:基于直播美颜SDK的完整解决方案
  • 可视化应急指挥平台在应急通信中的优势
  • 视觉目标检测标注xml格式文件解析可视化 - python 实现
  • 【数据结构】五分钟自测主干知识(十二)
  • 两步GMM计算权重矩阵
  • HTML5新增属性
  • 蓝桥杯练习笔记(十九-质数筛)
  • Github 2024-10-27 php开源项目日报 Top10
  • 【verilog】模十计数器
  • 电商直播带货乱象频出,食品经销商如何规避高额损失?
  • Word 每次打开时都会弹出“要还原的文件”对话框
  • iframe视频宽度高度自适应( pc+移动都可以用,jq写法 )
  • Unity控制物体透明度的改变
  • Matplotlib 网格线
  • PostgreSQL 删除角色