首页 > 其他分享 >恋爱脑学Rust之闭包三Traits:Fn,FnOnce,FnMut

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

时间:2024-11-01 18:19:19浏览次数:7  
标签:闭包 closure 调用 包三 FnMut 之闭 report FnOnce Fn

在这里插入图片描述

在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 中设计灵活又安全的闭包调用方式,从而更好地控制资源和状态。

标签:闭包,closure,调用,包三,FnMut,之闭,report,FnOnce,Fn
From: https://blog.csdn.net/gzjimzhou/article/details/143394111

相关文章

  • python学习之闭包与装饰器
    一、闭包闭包允许一个函数访问并操作函数外部的变量(即父级作用域中的变量),即使在该函数外部执行。特性:(1)外部函数嵌套内部函数。(2)外部函数可以返回内部函数。(3)内部函数可以访问外部函数的局部变量。defout():print("我是外层")n=10defins():......
  • ERP发展阶段三之闭环MRP
    闭环MRP时段式MRP能根据有关数据计算出相关物料需求的准确时间与数量,但它还不够完善,其主要缺陷是没有考虑到生产企业现有的生产能力和采购的有关条件约束。因此计算出来的物料需求日期有可能因设备和工时的不足而没有能力生产,或者因原料的不足而无法生产。同时,它也缺乏根据计划......
  • 使用IDEA打jar包三种方式 步骤(包含跳过测试模式)
    使用IDEA打jar包三种方式步骤(包含跳过测试模式)正文:方式一:网络最常见的打包方式。比较适用于普通项目打JAR包。方式二:比较适用于MAVEN项目打JAR包。方式三:maven界面。方式一:网络最常见的打包方式。比较适用于普通项目打JAR包。选中菜单栏中File–>ProjectStructure(文件–>项......
  • 一个脚本打包三个平台的linux,mac,windows的golang程序
    一个脚本打包三个平台的linux,mac,windows的golang程序:#!/bin/bash#设置变量APP_NAME="atmp"VERSION="1.0.0"BUILD_TIME=$(date+%Y-%m-%d_%H:%M:%S)BUILD_DIR="bin"PLATFORMS=("linux/amd64""windows/amd64""darwin/amd64&......
  • Python笔记三之闭包与装饰器
    本文首发于公众号:Hunter后端原文链接:Python笔记三之闭包与装饰器这一篇笔记介绍Python里面的装饰器。在介绍装饰器前,首先提出这样一个需求,我想统计某个函数的执行时间,假设这个函数如下:importtimedefadd(x,y):time.sleep(1)returnx+y想要统计add函数......
  • Python高级之闭包函数
    闭包函数【一】闭包函数的定义闭包(Closure)是指在函数内部定义的函数,并且这个内部函数可以访问外部函数的变量。这种机制允许函数保留对它创建时可见的变量的访问权,即使在其生命周期结束后也可以使用。闭包的主要特点是:内部函数定义在外部函数内部。内部函数可以引用外部函数......
  • Python深入分享之闭包
    闭包(closure)是函数式编程的重要的语法结构。函数式编程是一种编程范式(而面向过程编程和面向对象编程也都是编程范式)。在面向过程编程中,我们见到过函数(function);在面向对象编程中,我们见过对象(object)。函数和对象的根本目的是以某种逻辑方式组织代码,并提高代码的可重复使用性......
  • JS加密/解密之闭包的运用
    深入探讨JavaScript闭包的演变与应用摘要:本文将深入探讨JavaScript闭包的概念、特性以及其在实际开发中的应用。我们将从闭包的起源开始,探讨它在JavaScript编程中的重要性,并通过实例展示闭包在不同场景下的灵活应用。引言JavaScript作为一种高度灵活的编程语言,一直以其独特的特性......
  • 三菱FX5U系列PLC控制10轴设备成套资料打包三菱FX5U控制10轴伺服的设备成套电气图纸,PLC
    三菱FX5U系列PLC控制10轴设备成套资料打包三菱FX5U控制10轴伺服的设备成套电气图纸,PLC程序,触摸屏程序,原理图,电气元件布局图,运行视频,伺服参数设置,Bom清单,操作说明书。PLC程序带有详细注释,时间锁,通讯,产品多个版型可选,操作方便,设备已经投产,运行稳定。此套程序为一手资料,伺服走脉冲不......
  • javascript基础知识之闭包和递归
    一,什么是闭包,会出现什么问题?如何避免?1、函数里面包含的子函数,子函数访问父函数的局部变量2、通过return将子函数暴露在全局作用域,子函数就形成闭包3、通过闭包,父函数的局......