学习 Rust 第 23 天:闭包

news2025/1/8 6:02:37

Rust 闭包提供了简洁、富有表现力的匿名函数来捕获周围的变量。它们简化了代码,提供了存储、参数传递和函数重构方面的灵活性。它们与泛型的交互增强了灵活性,而捕获模式则促进了有效的所有权和可变性管理。从本质上讲,闭包是 Rust 的基础,可以提高代码的清晰度和简洁性。

Introduction 介绍

We used a closure in themini_grep project, here’s what it looked like
我们在 mini_grep 项目中使用了闭包,如下所示

let config = Config::new(&args).unwrap_or_else(|err|{
        eprintln!("Problem parsing arguments: {}",err);
        eprintln!("Expected: {} search_query filename", args[0]);
        process::exit(1);
    });

Closures are like anonymous functions, and we can store them in variables as well, pass them as function arguments etc.
闭包就像匿名函数,我们也可以将它们存储在变量中,将它们作为函数参数传递等。

In Rust, closures are anonymous functions that can capture variables from their surrounding environment. They are similar to lambda functions or blocks in other programming languages. Closures in Rust have a flexible and powerful syntax, allowing them to be used in various contexts.
在 Rust 中,闭包是可以从周围环境捕获变量的匿名函数。它们类似于其他编程语言中的 lambda 函数或块。 Rust 中的闭包具有灵活而强大的语法,允许它们在各种上下文中使用。

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

    let result = add_one(5);
    println!("Result: {}", result); // Output: 6
}

In this example, add_one is a closure that takes one parameter x and returns x + 1. It's assigned to a variable, and then called with an argument 5. The result is printed out.
在此示例中, add_one 是一个闭包,它采用一个参数 x 并返回 x + 1 。它被分配给一个变量,然后使用参数 5 进行调用。结果被打印出来。

This is a basic example of what a closure looks like.
这是闭包的基本示例。

Refactoring Functions to Closures
将函数重构为闭包

Refactoring functions with closures can often lead to more concise and expressive code.
使用闭包重构函数通常可以生成更简洁、更具表现力的代码。
Let’s consider a function that applies a given operation to each element in a vector:
让我们考虑一个将给定操作应用于向量中每个元素的函数:

fn apply_operation_to_vec(values: Vec<i32>, operation: fn(i32) -> i32) -> Vec<i32> {
    let mut result = Vec::new();
    for value in values {
        result.push(operation(value));
    }
    result
}

fn double(x: i32) -> i32 {
    x * 2
}

fn main() {
    let values = vec![1, 2, 3, 4, 5];
    let doubled_values = apply_operation_to_vec(values, double);
    println!("Doubled values: {:?}", doubled_values);
}

In this code, we have a function apply_operation_to_vec that takes a vector of integers and a function pointer operation representing the operation to be applied to each element of the vector.
在此代码中,我们有一个函数 apply_operation_to_vec ,它接受一个整数向量和一个表示要应用于向量每个元素的操作的函数指针 operation 。

Now, let’s refactor this function to use closures instead:
现在,让我们重构这个函数以使用闭包:

fn apply_operation_to_vec(values: Vec<i32>, operation: impl Fn(i32) -> i32) -> Vec<i32> {
    values.into_iter().map(operation).collect()
}

fn main() {
    let values = vec![1, 2, 3, 4, 5];
    let doubled_values = apply_operation_to_vec(values, |x| x * 2);
    println!("Doubled values: {:?}", doubled_values);
}

In this refactored version:
在这个重构版本中:

  • We’ve changed the operation parameter to accept a closure instead of a function pointer. The impl Fn(i32) -> i32 syntax means that operation can accept any type that implements the Fn(i32) -> i32 trait, which includes closures.
    我们更改了 operation 参数以接受闭包而不是函数指针。 impl Fn(i32) -> i32 语法意味着 operation 可以接受任何实现 Fn(i32) -> i32 特征的类型,其中包括闭包。
  • Inside the apply_operation_to_vec function, we use into_iter() to consume the input vector and produce an iterator. Then, we use map() to apply the closure to each element, and collect() to collect the results into a new vector.
    在 apply_operation_to_vec 函数内,我们使用 into_iter() 来使用输入向量并生成迭代器。然后,我们使用 map() 将闭包应用于每个元素,并使用 collect() 将结果收集到新的向量中。

Both output the same data:
两者输出相同的数据:

$ cargo run
Doubled values: [2, 4, 6, 8, 10]

Type annotations 类型注释

To declare an add_one function which returns an integer, and takes an integer as a parameter. The function declaration would look something like this
声明一个返回整数并接受整数作为参数的 add_one 函数。函数声明看起来像这样

fn add_one(x: i32)->i32{
  x+1
}

But If I want to do the same with closures, notice how I don’t need to specify any data types here…
但是如果我想对闭包做同样的事情,请注意我不需要在这里指定任何数据类型......

fn main(){
  let add_one = |x| x + 1;
}

The add_one closure is defined without specifying the types of its parameters and return value. Rust's type inference mechanism automatically deduces that add_one takes an i32 parameter and returns an i32 value.
add_one 闭包的定义没有指定其参数和返回值的类型。 Rust 的类型推断机制会自动推断 add_one 接受 i32 参数并返回 i32 值。

Generic parameters and Function traits
通用参数和函数特征

Fn trait:  Fn 功能:

  • Closures that implement Fn can be called immutably.
    实现 Fn 的闭包可以被不可变地调用。
  • They capture their environment by reference, allowing them to borrow variables from the surrounding scope.
    它们通过引用捕获环境,从而允许它们从周围范围借用变量。
  • This trait is suitable for closures that don’t need to mutate the captured variables.
    此特性适用于不需要改变捕获变量的闭包。
  • Example usage: Functions that only read from the captured variables.
    用法示例:仅读取捕获变量的函数。

FnMut trait:  FnMut 功能:

  • Closures that implement FnMut can be called mutably.
    实现 FnMut 的闭包可以被可变地调用。
  • They capture their environment by mutable reference, allowing them to modify the captured variables.
    他们通过可变引用捕获环境,从而允许他们修改捕获的变量。
  • This trait is suitable for closures that need to mutate the captured variables.
    此特性适用于需要改变捕获变量的闭包。
  • Example usage: Functions that modify the captured variables but don’t consume them.
    用法示例:修改捕获的变量但不消耗它们的函数。

FnOnce trait:  FnOnce 功能:

  • Closures that implement FnOnce take ownership of the captured variables.
    实现 FnOnce 的闭包拥有捕获的变量的所有权。
  • They can only be called once because they consume the captured variables.
    它们只能被调用一次,因为它们消耗捕获的变量。
  • This trait is suitable for closures that need to consume the captured variables, transferring ownership to the closure.
    此特性适用于需要使用捕获的变量、将所有权转移给闭包的闭包。
  • Example usage: Functions that consume the captured variables, such as closures used in move semantics.
    用法示例:使用捕获变量的函数,例如移动语义中使用的闭包。

In Rust, you can use generic parameters with function traits (FnFnMutFnOnce) to make functions more flexible and reusable across different types.
在 Rust 中,您可以将泛型参数与函数特征( Fn 、 FnMut 、 FnOnce )一起使用,以使函数在不同类型之间更加灵活和可重用。

// A generic function that takes a closure and applies it to an input value
fn apply_closure<F, T>(closure: F, value: T) -> T
where
    F: Fn(T) -> T,
{
    closure(value)
}

fn main() {
    // Define a closure that doubles an integer
    let double_closure = |x: i32| x * 2;

    // Apply the closure to a value
    let result = apply_closure(double_closure, 5);
    println!("Result: {}", result); // Output: Result: 10

    // Define a closure that appends a string
    let append_closure = |s: String| s + " World";

    // Apply the closure to a value
    let result = apply_closure(append_closure, String::from("Hello"));
    println!("Result: {}", result); // Output: Result: Hello World
}
  • We have a generic function apply_closure that takes two parameters: a closure (F) and a value (T). The closure must implement the Fn(T) -> T trait, meaning it takes a single parameter of type T and returns a value of type T.
    我们有一个通用函数 apply_closure ,它接受两个参数:一个闭包 ( F ) 和一个值 ( T )。闭包必须实现 Fn(T) -> T 特征,这意味着它采用 T 类型的单个参数并返回 T 类型的值。
  • Inside main(), we define two closures: double_closure and append_closure, each with different input and output types (i32 and String).
    在 main() 内部,我们定义了两个闭包: double_closure 和 append_closure ,每个闭包都有不同的输入和输出类型( i32 和 String
  • We then call apply_closure twice, passing each closure along with an appropriate value. The function applies the closure to the value and returns the result.
    然后我们调用 apply_closure 两次,传递每个闭包以及适当的值。该函数将闭包应用于该值并返回结果。

This approach allows us to use the same generic function with different closures, making our code more reusable and flexible. Additionally, by specifying the Fn trait bound, we ensure that the closures passed to apply_closure are callable and match the expected signature.
这种方法允许我们使用具有不同闭包的相同泛型函数,使我们的代码更加可重用和灵活。此外,通过指定 Fn 特征边界,我们确保传递给 apply_closure 的闭包是可调用的并且与预期签名匹配。

Capturing the environment with closures
用闭包捕捉环境

In Rust, closures can capture variables from their surrounding environment. This feature allows closures to access and manipulate variables that are defined outside of their own scope. Rust provides three ways for closures to capture variables: by reference (&T), by mutable reference (&mut T), or by value (T).
在 Rust 中,闭包可以从周围环境捕获变量。此功能允许闭包访问和操作在其自身范围之外定义的变量。 Rust 提供了三种闭包捕获变量的方法:通过引用 ( &T )、通过可变引用 ( &mut T ) 或通过值 ( T )。

Let's explore each method:
让我们探讨一下每种方法:

Capture by reference (&T):
通过引用捕获 ( &T ):

  • When a closure captures variables by reference, it borrows them immutably.
    当闭包通过引用捕获变量时,它会不可变地借用它们。
  • The closure can read but cannot modify the variables it captures.
    闭包可以读取但不能修改它捕获的变量。
  • The captured variables remain accessible and can be used after the closure’s execution.
    捕获的变量仍然可以访问,并且可以在闭包执行后使用。
  • This is the default behavior for closures that don’t explicitly specify how they capture variables.
    这是未明确指定如何捕获变量的闭包的默认行为。
fn main() {
    let x = 42;
    let closure = || {
        println!("Captured value: {}", x);
    };
    closure();
    // x is still accessible here
    println!("Outer value: {}", x);
}

Capture by mutable reference (&mut T):
通过可变引用捕获( &mut T ):

  • When a closure captures variables by mutable reference, it borrows them mutably.
    当闭包通过可变引用捕获变量时,它会可变地借用它们。
  • The closure can read and modify the variables it captures.
    闭包可以读取和修改它捕获的变量。
  • The captured variables remain mutable after the closure’s execution.
    捕获的变量在闭包执行后仍然可变。
  • To capture variables by mutable reference, the closure must be declared with the mut keyword.
    要通过可变引用捕获变量,必须使用 mut 关键字声明闭包。
fn main() {
    let mut x = 42;
    let mut closure = || {
        println!("Captured value before: {}", x);
        x += 1;
        println!("Captured value after: {}", x);
    };
    closure();
    // x is still accessible here, and its value has been modified by the closure
    println!("Outer value: {}", x);
}

Capture by value (T):
按值捕获 ( T ):

  • When a closure captures variables by value, it takes ownership of them.
    当闭包按值捕获变量时,它就获得了它们的所有权。
  • The closure can consume the variables it captures.
    闭包可以消耗它捕获的变量。
  • After the closure’s execution, the captured variables are moved into the closure and no longer accessible in the outer scope.
    闭包执行后,捕获的变量将移至闭包中,并且无法再在外部作用域中访问。
fn main() {
    let x = 42;
    let closure = move || {
        println!("Captured value: {}", x);
    };
    closure();
    // x is not accessible here; it has been moved into the closure
    // println!("Outer value: {}", x); // This line would cause a compilation error
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1636238.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

笔记-用Python脚本启停JAR程序

用Python脚本启停JAR程序&#xff0c;需要用到python中的以下内置模块 subprocess 是 Python 的一个标准库模块&#xff0c;用于在新进程中执行子命令&#xff0c;获取子进程的输入/输出/错误以及返回码等os 是 Python 的一个标准库模块&#xff0c;它提供了与操作系统交互的功…

【web安全】-- 命令执行漏洞详解

本文将从原理开始介绍命令执行漏洞并附有三个实例来供各位客官学习 文章目录 一、什么是命令执行漏洞二、出现的原因三、有可能存在命令执行漏洞的函数&#xff08;php&#xff09;1、利用一些函数来实现命令执行2、直接执行系统命令的函数 四、命令拼接符号1、Windows2、linux…

【Mac】mac 安装 prometheus 报错 prometheus: prometheus: cannot execute binary file

1、官网下载 Download | Prometheus 这里下载的是prometheus-2.51.2.linux-amd64.tar.gz 2、现象 解压之后启动Prometheus 启动脚本&#xff1a; nohup ./prometheus --config.fileprometheus.yml > prometheus.out 2>&1 & prometheus.out日志文件&#xff…

opencv基础篇 ——(十)非真实感渲染

非真实感渲染&#xff08;Non-Photorealistic Rendering, NPR&#xff09;是指通过一系列图像处理技术&#xff0c;将真实感图像转换为具有特定艺术风格或视觉效果的图像&#xff0c;模拟绘画、素描、卡通等非现实主义表现手法。OpenCV 提供了一些内置函数来实现非真实感渲染&a…

医疗大模型华佗GPT-2:医学问答超越GPT-4,通过2023年国家执业药师考试

前言 随着人工智能技术的快速发展&#xff0c;特别是在自然语言处理(NLP)领域&#xff0c;大型预训练模型如GPT系列已经显示出在多个领域的强大应用潜力。最近&#xff0c;华佗GPT-2医疗大模型的发布&#xff0c;不仅标志着人工智能在医学领域的一大进步&#xff0c;更是在202…

Linux IP Forwarding路由转发实验

linux 路由转发功能 Linux 操作系统具备路由转发功能&#xff0c;路由功能是指 Linux 操作系统提供的路由管理和转发功能&#xff0c;它允许 Linux 主机在网络中正确地转发数据包&#xff0c;并确保数据包能够达到其目的地。 出于安全考虑&#xff0c;Linux系统默认是禁止数据…

vscode 检查更新 没有检查更新按钮

vscode 检查更新 没有检查更新按钮 1、问题描述2、问题分析3、解决方法 1、问题描述 今天在使用vscode写markdown文档时&#xff0c;需要粘贴图片到markdown文档中&#xff0c;结果无法粘贴进来&#xff0c;显示如下&#xff1a;只粘贴了image.png这几个字。 2、问题分析 搜索…

交通运输智慧监管平台---强化物流安全与效率的新举措

一、建设背景 随着社会对于交通安全和环境保护的要求不断提高&#xff0c;对卡车运输的监管和合规性要求也逐渐加强。为了满足快速发展的物流需求&#xff0c;提高供应链协同和可追溯性、解决安全问题、提高运输效率和降低成本&#xff0c;我们利用现代技术和信息化手段着力建设…

QT - 创建Qt Widgets Application项目

在Qt中结合OpenGL使用&#xff0c;可以创建一个Qt Widgets应用程序项目。在创建项目时&#xff0c;您可以选择使用OpenGL模板来生成一个已经集成了OpenGL的项目。这个模板会自动帮助您集成OpenGL和Qt&#xff0c;并生成一个基本的OpenGL窗口。您可以在这个窗口中进行OpenGL的开…

搭建大型分布式服务(三十七)SpringBoot 整合多个kafka数据源-取消限定符

系列文章目录 文章目录 系列文章目录前言一、本文要点二、开发环境三、原项目四、修改项目五、测试一下五、小结 前言 本插件稳定运行上百个kafka项目&#xff0c;每天处理上亿级的数据的精简小插件&#xff0c;快速上手。 <dependency><groupId>io.github.vipjo…

Spark SQL编程初级实践

参考链接 Spark编程: Spark SQL基本操作 2020.11.01_df.agg("age"->"avg")-CSDN博客 RDD编程初级实践-CSDN博客 Spark和Hadoop的安装-CSDN博客 1. Spark SQL基本操作 { "id":1 , "name":" Ella" , "age":…

深入理解正则表达式:从入门到精通

title: 深入理解正则表达式&#xff1a;从入门到精通 date: 2024/4/30 18:37:21 updated: 2024/4/30 18:37:21 tags: 正则Python文本分析日志挖掘数据清洗模式匹配工具推荐 第一章&#xff1a;正则表达式入门 介绍正则表达式的基本概念和语法 正则表达式是一种用于描述字符串…

Docker安装Nginx端口映射|挂载目录查看

文章目录 前言Docker安装nginx怎么查看端口映射|挂载映射 前言 Docker 的作用主要体现在应用隔离、跨平台部署、快速部署、环境一致性以及资源隔离和管理等方面&#xff0c;为软件开发和运维带来了很多便利和优势。 在服务器&#xff08;虚拟机&#xff09;中安装 Nginx、MyS…

从源头上减少BUG:掌握Java中的label和assert语句!

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一个人虽可以走的更快&#xff0c;但一群人可以走的更远。 我是一名后…

【分享】如何将word格式文档转化为PDF格式

在日常的办公和学习中&#xff0c;我们经常需要将Word文档转换为PDF格式。PDF作为一种通用的文件格式&#xff0c;具有跨平台、易读性高等优点&#xff0c;因此在许多场合下都更为适用。那么&#xff0c;如何实现Word转PDF呢&#xff1f;本文将介绍几种常用的方法&#xff0c;帮…

巧用 TiCDC Syncpiont 构建银行实时交易和准实时计算一体化架构

本文阐述了某商业银行如何利用 TiCDC Syncpoint 功能&#xff0c;在 TiDB 平台上构建一个既能处理实时交易又能进行准实时计算的一体化架构&#xff0c;用以优化其零售资格业务系统的实践。通过迁移到 TiDB 并巧妙应用 Syncpoint&#xff0c;该银行成功解决了原有多个 MySQL 集…

Ubuntu C++ man手册安装及使用

Ubuntu下C++ man手册安装 C++在线文档: http://www.cplusplus.com/reference/ 第一种办法:使用cppman $ sudo apt install cppman 使用方法 第二种办法: 打开网页:GCC mirror sites- GNU Project 点击下图中的突显行链接: Russia, Novosibirsk:

使用groovy+spock优雅的进行单测

使用groovyspock优雅的进行单测 1. groovyspock示例1.1 简单示例1.2 增加where块的示例1.3 实际应用的示例 2. 单测相关问题2.1 与SpringBoot融合2.2 单测数据与测试数据隔离2.3 SQL自动转换&#xff08;MySQL -> H2&#xff09; 参考 Groovy是一种基于JVM的动态语言&#x…

安卓获取SHA

1&#xff1a;安卓通过签名key获取SHA 方式有两种&#xff0c; 1、电脑上来存在eclipse的用户或正在使用此开发工具的用户就简单了&#xff0c;直接利用eclipse 走打包流程&#xff0c;再打包的时候选择相应的签名&#xff0c;那么在当前面板的下面便会出现签名的相关信息。 2、…

Java根据模板动态生成Pdf(添加页码、文件加密、Spire免费版本10页之后无法显示问题、嵌入图片添加公章、转Base64)

Java根据模板动态生成Pdf&#xff1a;添加页码、文件加密、Spire免费版本10页之后无法显示问题、嵌入图片添加公章、转Base64 引言【Java根据模板动态生成Pdf资源地址】示例一&#xff1a;动态生成带页码的PDF报告示例二&#xff1a;加密PDF以保护敏感信息示例三&#xff1a;应…