rust 闭包

【Rust闭包】rust语言闭包函数原理用法汇总与应用实战 闭包(Closure)是 Rust 中一个强大且灵活的特性,它允许你捕获环境中的变量并在稍后执行。Rust 的闭包设计既高效又安全,是函数式编程风格的重要组成部分。 Rust 的 闭包(closures)是可以保存进变量或作为参数传递给其他函数的匿名函数。 可以在一个地方创建闭包,然后在不同的上下文中执行闭包运算。 不同于函数,闭包允许捕获调用者作用域中的值。 阅读详情

闭包

闭包是一种匿名函数,它可以赋值给变量也可以作为参数传递给其它函数,不同于函数的是,它允许捕获调用者作用域中的值,例如:

fn main() {
   let x = 1;
   let sum = |y| x + y;

    assert_eq!(3, sum(2));
}

上面的代码展示了非常简单的闭包 sum,它拥有一个入参 y,同时捕获了作用域中的 x 的值,因此调用 sum(2) 意味着将 2(参数 y)跟 1(x)进行相加,最终返回它们的和:3。

可以看到 sum 非常符合闭包的定义:可以赋值给变量,允许捕获调用者作用域中的值。

使用闭包来简化代码
传统函数实现

想象一下,我们要进行健身,用代码怎么实现,这里是我的想法:

use std::thread;
use std::time::Duration;

// 开始健身,好累,我得发出声音:muuuu...
fn muuuuu(intensity: u32) -> u32 {
    println!("muuuu.....");
    thread::sleep(Duration::from_secs(2));
    intensity
}

fn workout(intensity: u32, random_number: u32) {
    if intensity < 25 {
        println!(
            "今天活力满满,先做 {} 个俯卧撑!",
            muuuuu(intensity)
        );
        println!(
            "旁边有妹子在看,俯卧撑太low,再来 {} 组卧推!",
            muuuuu(intensity)
        );
    } else if random_number == 3 {
        println!("昨天练过度了,今天还是休息下吧!");
    } else {
        println!(
            "昨天练过度了,今天干干有氧,跑步 {} 分钟!",
            muuuuu(intensity)
        );
    }
}

fn main() {
    // 强度
    let intensity = 10;
    // 随机值用来决定某个选择
    let random_number = 7;

    // 开始健身
    workout(intensity, random_number);
}

可以看到,在健身时我们根据想要的强度来调整具体的动作,然后调用 muuuuu 函数来开始健身。这个程序本身很简单,没啥好说的,但是假如未来不用 muuuuu 函数了,是不是得把所有 muuuuu 都替换成,比如说 woooo ?如果 muuuuu 出现了几十次,那意味着我们要修改几十处地方。

闭包实现
use std::thread;
use std::time::Duration;

fn workout(intensity: u32, random_number: u32) {
    let action = || {
        println!("muuuu.....");
        thread::sleep(Duration::from_secs(2));
        intensity
    };

    if intensity < 25 {
        println!(
            "今天活力满满,先做 {} 个俯卧撑!",
            action()
        );
        println!(
            "旁边有妹子在看,俯卧撑太low,再来 {} 组卧推!",
            action()
        );
    } else if random_number == 3 {
        println!("昨天练过度了,今天还是休息下吧!");
    } else {
        println!(
            "昨天练过度了,今天干干有氧,跑步 {} 分钟!",
            action()
        );
    }
}

fn main() {
    // 动作次数
    let intensity = 10;
    // 随机值用来决定某个选择
    let random_number = 7;
FnOnce,该类型的闭包会拿走被捕获变量的所有权。Once 顾名思义,说明该闭包只能运行一次:
    // 开始健身
    workout(intensity, random_number);
}

在上面代码中,无论你要修改什么,只要修改闭包 action 的实现即可,其它地方只负责调用,完美解决了我们的问题!

Rust 闭包在形式上借鉴了 Smalltalk 和 Ruby 语言,与函数最大的不同就是它的参数是通过 |parm1| 的形式进行声明,如果是多个参数就 |param1, param2,…|, 下面给出闭包的形式定义:

|param1, param2,...| {
    语句1;
    语句2;
    返回表达式
}
闭包的类型推导

与函数相反,闭包并不会作为 API 对外提供,因此它可以享受编译器的类型推导能力,无需标注参数和返回值的类型。
下面展示了同一个功能的函数和闭包实现形式:

fn  add_one_v1   (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x|             { x + 1 };
let add_one_v4 = |x|               x + 1  ;

虽然类型推导很好用,但是它不是泛型,当编译器推导出一种类型后,它就会一直使用该类型:

let example_closure = |x| x;

let s = example_closure(String::from("hello"));
let n = example_closure(5);

首先,在 s 中,编译器为 x 推导出类型 String,但是紧接着 n 试图用 5 这个整型去调用闭包,跟编译器之前推导的 String 类型不符,因此报错:

error[E0308]: mismatched types
 --> src/main.rs:5:29
  |
5 |     let n = example_closure(5);
  |                             ^
  |                             |
  |                             expected struct `String`, found integer // 期待String类型,却发现一个整数
  |                             help: try using a conversion method: `5.to_string()`

结构体中的闭包

假设我们要实现一个简易缓存,功能是获取一个值,然后将其缓存起来,那么可以这样设计:

  • 一个闭包用于获取值
  • 一个变量,用于存储该值
    可以使用结构体来代表缓存对象,最终设计如下:
struct Cacher<T>
where
    T: Fn(u32) -> u32,
{
    query: T,
    value: Option<u32>,
}

特征 Fn(u32) -> u32 从表面来看,就对闭包形式进行了显而易见的限制:该闭包拥有一个u32类型的参数,同时返回一个u32类型的值。

接着,为缓存实现方法:

impl<T> Cacher<T>
where
    T: Fn(u32) -> u32,
{
    fn new(query: T) -> Cacher<T> {
        Cacher {
            query,
            value: None,
        }
    }

    // 先查询缓存值 `self.value`,若不存在,则调用 `query` 加载
    fn value(&mut self, arg: u32) -> u32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.query)(arg);
                self.value = Some(v);
                v
            }
        }
    }
}

上面的缓存有一个很大的问题:只支持 u32 类型的值,若我们想要缓存 &str 类型,显然就行不通了,因此需要将 u32 替换成泛型 E,该练习就留给读者自己完成,具体代码可以参考这里

捕获作用域中的值

在之前代码中,我们一直在用闭包的匿名函数特性(赋值给变量),然而闭包还拥有一项函数所不具备的特性:捕获作用域中的值。

fn main() {
    let x = 4;

    let equal_to_x = |z| z == x;

    let y = 4;

    assert!(equal_to_x(y));
}

上面代码中,x 并不是闭包 equal_to_x 的参数,但是它依然可以去使用 x,因为 equal_to_x 在 x 的作用域范围内。

对于函数来说,就算你把函数定义在 main 函数体中,它也不能访问 x:

fn main() {
    let x = 4;

    fn equal_to_x(z: i32) -> bool {
        z == x
    }

    let y = 4;

    assert!(equal_to_x(y));
}

报错如下:

error[E0434]: can't capture dynamic environment in a fn item // 在函数中无法捕获动态的环境
 --> src/main.rs:5:14
  |
5 |         z == x
  |              ^
  |
  = help: use the `|| { ... }` closure form instead // 使用
三种 Fn 特征
  1. FnOnce,该类型的闭包会拿走被捕获变量的所有权。Once 顾名思义,说明该闭包只能运行一次:
fn fn_once<F>(func: F)
where
    F: FnOnce(usize) -> bool,
{
    println!("{}", func(3));
    println!("{}", func(4));
}

fn main() {
    let x = vec![1, 2, 3];
    fn_once(|z|{z == x.len()})
}
  1. FnMut,它以可变借用的方式捕获了环境中的值,因此可以修改该值:
fn main() {
    let mut s = String::new();

    let update_string =  |str| s.push_str(str);
    update_string("hello");

    println!("{:?}",s);
}
  1. Fn 特征,它以不可变借用的方式捕获环境中的值 让我们把上面的代码中 exec 的 F 泛型参数类型修改为 Fn(&'a str):
fn main() {
    let mut s = String::new();

    let update_string =  |str| s.push_str(str);

    exec(update_string);

    println!("{:?}",s);
}

fn exec<'a, F: Fn(&'a str)>(mut f: F)  {
    f("hello")
}
闭包作为函数返回值

但是如果要使用闭包作为函数返回值,该如何做?

先来看一段代码:

fn factory() -> Fn(i32) -> i32 {
    let num = 5;

    |x| x + num
}

let f = factory();

let answer = f(1);
assert_eq!(6, answer);

上面这段代码看起来还是蛮正常的,用 Fn(i32) -> i32 特征来代表 |x| x + num,非常合理嘛,肯定可以编译通过, 可惜理想总是难以照进现实,编译器给我们报了一大堆错误,先挑几个重点来看看:

fn factory<T>() -> Fn(i32) -> i32 {
  |                    ^^^^^^^^^^^^^^ doesn't have a size known at compile-time // 该类型在编译器没有固定的大小

Rust 要求函数的参数和返回类型,必须有固定的内存大小,例如 i32 就是 4 个字节,引用类型是 8 个字节,总之,绝大部分类型都有固定的大小,但是不包括特征,因为特征类似接口,对于编译器来说,无法知道它后面藏的真实类型是什么,因为也无法得知具体的大小。

同样,我们也无法知道闭包的具体类型,该怎么办呢?再看看报错提示:

help: use `impl Fn(i32) -> i32` as the return type, as all return paths are of type `[closure@src/main.rs:11:5: 11:21]`, which implements `Fn(i32) -> i32`
  |
8 | fn factory<T>() -> impl Fn(i32) -> i32 {

嗯,编译器提示我们加一个 impl 关键字,哦,这样一说,读者可能就想起来了,impl Trait 可以用来返回一个实现了指定特征的类型,那么这里 impl Fn(i32) -> i32 的返回值形式,说明我们要返回一个闭包类型,它实现了 Fn(i32) -> i32 特征。

完美解决,但是,在特征那一章,我们提到过,impl Trait 的返回方式有一个非常大的局限,就是你只能返回同样的类型,例如:

fn factory(x:i32) -> impl Fn(i32) -> i32 {

    let num = 5;

    if x > 1{
        move |x| x + num
    } else {
        move |x| x - num
    }
}

运行后,编译器报错:

error[E0308]: `if` and `else` have incompatible types
  --> src/main.rs:15:9
   |
12 | /     if x > 1{
13 | |         move |x| x + num
   | |         ---------------- expected because of this
14 | |     } else {
15 | |         move |x| x - num
   | |         ^^^^^^^^^^^^^^^^ expected closure, found a different closure
16 | |     }
   | |_____- `if` and `else` have incompatible types
   |

嗯,提示很清晰:if 和 else 分支中返回了不同的闭包类型,这就很奇怪了,明明这两个闭包长的一样的,好在细心的读者应该回想起来,本章节前面咱们有提到:就算签名一样的闭包,类型也是不同的,因此在这种情况下,就无法再使用 impl Trait 的方式去返回闭包。

只需要用 Box 的方式即可实现:

fn factory(x:i32) -> Box<dyn Fn(i32) -> i32> {
    let num = 5;

    if x > 1{
        Box::new(move |x| x + num)
    } else {
        Box::new(move |x| x - num)
    }
}

至此,闭包作为函数返回值就已完美解决,若以后你再遇到报错时,一定要仔细阅读编译器的提示,很多时候,转角都能遇到爱。

Claude Code vs Codex实战对比:哪个更适合你的Next.js项目?(含真实案例) 本文通过两个真实的Next.js项目案例,深度对比了Claude Code与Codex在UI实现和业务逻辑构建中的表现。Claude Code在代码质量、设计还原度和架构完整性方面优势明显,适合对可维护性要求高的生产级项目;而Codex则以快速响应和低成本见长,更适合原型开发或内部工具。文章提供了基于项目类型、团队能力和成本效益的详细选择策略。 阅读详情

相关推荐

无线图像传输新突破:深度JSCC算法在低带宽环境下的性能实测

本文深入探讨了基于深度学习的信源信道联合编码(JSCC)算法在低带宽无线图像传输中的突破性表现。通过实测数据对比,深度JSCC在低信噪比环境下展现出卓越的鲁棒性,有效对抗传统方案的“悬崖效应”,实现图像质量的平滑下降,为远程医疗、工业巡检等严苛场景提供了新的解决方案。

weixin_29090089的博客 508

【Rust 基础篇】Rust高级函数:函数作为参数与返回值

本篇博客将深入探讨Rust中高级函数的概念,包括函数作为参数传递、函数作为返回值返回,以及使用高级函数的一些常见模式,以便读者全面了解Rust中高级函数的使用方法。本篇博客深入探讨了Rust中高级函数的概念,包括函数作为参数传递、函数作为返回值返回,以及使用高级函数的一些常见模式。通过将函数作为参数传递,我们可以在不修改函数本身的情况下,改变函数的行为,使得代码更加灵活和可扩展。在Rust中,函数可以作为返回值返回,这使得我们可以返回一个特定函数,根据需要执行不同的逻辑。

繁依Fanyi的博客 2898

零基础小白Coze知识普及【新手必备资料!!】

AI智能体|扣子(Coze)工作流搭建【零基础小白Coze知识普及】 1、领取100+工作流源码!复制到扣子即可直接使用 2、AI认知+9节商业智能体拆解 3、500+各行业高质量提示词 4、超低价格使用即梦文生图图生视频和banana生图

Zhima0522的博客 3254

Rust从入门到实战系列三百四十四:闭包会捕获其环境

一个引用了其周围作用域中变量的闭包示例这里,即便 x 并不是 equal_to_x 的一个参数,equal_to_x 闭包也被允许使用变量 x,因为它与 equal_to_x。12 中,equal_to_x 闭包不可变的借用了 x(所以 equal_to_x 具有 Fn trait),因为闭包体只需要读取 x 的值。那些并没有移动被捕获变量的所有权到闭包。如果你希望强制闭包获取其使用的环境值的所有权,可以在参数列表前使用 move 关键字。费捕获到的变量,闭包必须获取其所有权并在定义闭包时将其移动进闭包。

wanmeijuhao的博客 520

YOLOv8中间特征层可视化

在通过观察这些特征,可以清楚地看到模型如何逐步将输入图像转化为有意义的表示。

weixin_45314480的博客 6891

堆内存管理核心逻辑 超详细复习笔记

本文详细解析了堆内存管理的核心逻辑,包括初始化、分配和释放三个关键环节。系统通过全局堆管理结构体和链表机制实现动态内存管理:初始化时标记整块内存为空闲;分配时遍历链表查找合适空闲块并进行分割;释放时定位内存块头并重新标记为空闲,支持相邻块合并以减少碎片。文章对比了链表式管理和线性分配的优缺点,指出链表式更灵活但实现复杂,线性分配简单但无法释放。最后总结了常见错误(如指针回推错误、碎片问题)和扩展知识(不同内存管理算法)。这套机制是理解嵌入式系统和RTOS内存管理的基础。

2502_92009732的博客 1204

rust实战系列一百二十五:闭包

例子中,虽然我们的multiple2函数定义在main函数体内,但是它无权访问main函数。们定义了一个普通函数,类型为fn(i32)->i32,也可以用于该参数中。相对而言,closure更像是一个的变量,它具有和变量同样的“生命。和普通函数一样,返回值也可以使用语句块表达式完成,与return语句的作用。只要函数定义在当前范围内是可以观察到的,就可以直接调用,不管在源码内。普通函数和闭包之间最大的区别是,普通函数不可以捕获环境变量。更进一步,如果闭包的语句体只包含一条语句,那么外层的大括号也可以省。

Mr_Roki的博客 479

LNA设计学习心得记录----MOS管的选取

LNA设计学习心得---MOS管的选取仿真

weixin_40359668的博客 6131

Rust:闭包作为参数和返回值

闭包作为参数和返回值 闭包作为参数(Taking closures as arguments) 现在我们知道了闭包是trait,我们已经知道了如何接受和返回闭包;就像任何其它的trait! 这也意味着我们也可以选择静态或动态分发。首先,让我们写一个获取可调用结构的函数,调用它,然后返回结果: where F : Fn(i32) -> i32 { ...

chenxuanhanhao的专栏 3019

运动控制软件:Siemens Sinamics二次开发_(6).通信接口与协议详解

PROFIBUSPROFINETModbus TCPCANopenEtherCAT每种通信接口都有其特定的应用场景和优势。选择合适的通信接口可以显著提高系统的性能和可靠性。在选择通信协议时,需要根据具体的应用场景和需求综合考虑。每种通信协议都有其特定的优势和局限性,因此在设计运动控制系统时,应选择最适合的协议,以确保系统的性能、可靠性、成本和维护性。通过以上对比和分析,希望开发者能够更好地理解和选择适合的通信接口和协议,从而优化运动控制系统的开发和应用。

2401_87715305的博客 901

VMware系列:此平台不支持虚拟化 Intel-VT-x/EPT 或 AMD-V/RVI

VMware系列:此平台不支持虚拟化 Intel-VT-x/EPT 或 AMD-V/RVI

weixin_54626591的博客 1万+

Rust闭包

闭包

qq_74143285的博客 925

QEMU源码全解析7 —— QEMU主函数

QEMU源码全解析7 —— QEMU主函数

phmatthaus的专栏 1295

【rust中的闭包】

简而言之,闭包就是可以直接访问其外部作用域的函数内部匿名函数(有点狭隘,不过大部分如此)。如果你只想调用一次,用Fnonce,如果你想改变用FnMut,如果你想在多线程中使用且不改变用Fn.“我们都无法确定选择是否正确,只是努力把选择变得正确.”

m0_37719524的博客 1425

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Rust的闭包

文章目录Rust的闭包创建闭包闭包捕获当前环境中的变量闭包作为函数参数 Rust的闭包 几乎每一种比C语言高级的语言都有闭包。Rust也同样支持闭包。闭包有一个很通俗的名称:匿名函数,它有着如下优点: 创建闭包不用为函数取名,方便快捷 闭包可以捕获调用者作用域中的值 闭包可以被保存进变量或作为参数传递给其他函数 创建闭包 Rust创建闭包的语法很简单:|参数列表| -> 返回类型 {代码段}。用两个竖线将函数参数包裹起来,后面接返回值的类型,最后是用花括号包裹的代码段就可以了。如: fn main

鸿鹄之志 4130

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

Rust 闭包

Rust 的闭包是可以保存在一个变量中或作为参数传递给其他函数的匿名函数。对一个城市序列进行排序。但是,此时如果将辅助函数写为会更加简洁。

编程回响 1390

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Rust 闭包详解

Rust闭包详解:匿名函数的强大工具 闭包是Rust中的匿名函数,具有捕获环境变量的能力,可作为一等公民使用。文章详细介绍了闭包的核心概念、语法结构、变量捕获机制以及实际应用场景。闭包通过实现Fn、FnMut或FnOnce trait来管理变量捕获,支持按值或引用捕获外部变量。其灵活的类型推断和所有权语义使其在迭代器、回调函数和多线程编程中表现优异。文章还探讨了如何在结构体中存储闭包、处理生命周期问题,以及在多线程环境下安全使用闭包的技术,包括move关键字和Arc<Mutex<T>&gt

本博客分享软硬件一体化开发实战。硬件涵盖 PCB 设计、STM32 等 MCU 底层开发;软件包含 C++、Rust,Qt/QML、PyQt、Slint、TouchGFX、LVGL 各类 GUI 框架。方便软硬件开发者查阅学习。欢迎关注! 1377

【Rust】——闭包

目录 🎯闭包会捕获其环境 🎯闭包类型推断和注解 🎯捕获引用或者移动所有权 🎯将捕获的值移除闭包和Fn trait

shsjssnn的博客 1160

rust闭包

(一)闭包是什么我们先来看看javascript中的闭包。在函数外部无法读取函数内的局部变量。但是我们有时候需要得到函数内的局部变量,那么如何从外部读取局部变量?那就是在函数的内部,再定义一个函数。在上面的代码中,函数f2在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是"链式作用域",子作用域会一级一级地向上寻找所有父作用域的变量。

void的博客 888

Rust语言中的闭包

闭包是 Rust 中的强大工具,它可以捕获外部环境的变量,并且像函数一样使用。FnFnMut和FnOnce,它们的区别在于闭包如何捕获并使用外部变量。闭包广泛应用于高阶函数、集合操作和事件处理等场景,是函数式编程的重要工具。

weixin_46063534的博客 875

rust学习-闭包

else {Noneindex: 0,// 使用闭包处理窗口.collect();println!("窗口和: {:?

weixin_42333247的博客 717

Rust闭包详解

闭包是可以捕获外部环境中的变量的匿名函数。它所引用的外部变量被称为自由变量(这些变量本不属于闭包函数的作用域范围)。与普通函数类似,闭包可以执行代码逻辑、接收参数输入,并返回值。闭包可以实现Fn、FnMut、FnOnce三个不同的trait,分别对应不同的行为。需要注意的是,这里Fn和定义函数指针类型使用的fn是不同的。绝大多数情况下,编译器根据上下文自动推断闭包应该实现哪个trait.需要注意的是。

Hello World! 475

Rust 中的闭包

什么是闭包?维基百科上对闭包是这样描述的: 在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持头等函数的编程语言中实现词法绑定的一种技术。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用),有些函数也可能没有自由变量。闭包跟函数

花落花开的博客 1298

Rust中的闭包

一直对 Rust 中的闭包似懂非懂, 用的时候基本都是以编译器不报错为准, 报错了就按编译器提示的改。今天有点时间,把 the book 中的关于闭包的部分从头到尾看了一遍,发现自己原来的理解不说是入木三分吧,至少也是南辕北辙。...

wangjun861205的博客 540
上一篇: Gradle笔记 六 Gradle 中的Dependencies
下一篇: 单例模式 rust和java的实现
过去日记
过去日记 新星创作者: 后端开发技术领域 新星创作者: 后端开发技术领域
博客等级 码龄3年 5343粉丝 225原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

过去日记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值