【Rust闭包】rust语言闭包函数原理用法汇总与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑

🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。

🏆《博客》:Rust开发,Python全栈,Golang开发,云原生开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏: Rust语言通关之路
景天的主页: 景天科技苑

文章目录

  • Rust闭包
    • [1. 闭包基础](#1. 闭包基础)
      • [1.1 什么是闭包](#1.1 什么是闭包)
      • [1.2 闭包的基本语法](#1.2 闭包的基本语法)
      • [1.3 闭包与函数的比较](#1.3 闭包与函数的比较)
    • [2. 闭包的捕获方式](#2. 闭包的捕获方式)
      • [2.1 Fn:不可变借用](#2.1 Fn:不可变借用)
      • [2.2 FnMut:可变借用](#2.2 FnMut:可变借用)
      • [2.3 FnOnce:获取所有权](#2.3 FnOnce:获取所有权)
      • [2.4 move关键字](#2.4 move关键字)
    • [3. 闭包作为参数和返回值](#3. 闭包作为参数和返回值)
      • [3.1 闭包作为函数参数](#3.1 闭包作为函数参数)
      • [3.2 闭包作为结构体字段](#3.2 闭包作为结构体字段)
      • [3.3 闭包作为函数返回值](#3.3 闭包作为函数返回值)
    • [4. 闭包的实际应用案例](#4. 闭包的实际应用案例)
      • [4.1 缓存/记忆化模式](#4.1 缓存/记忆化模式)
    • [5. 高级闭包技巧](#5. 高级闭包技巧)
      • [5.1 闭包与生命周期](#5.1 闭包与生命周期)
    • [6. 性能考虑](#6. 性能考虑)
    • [7. 总结](#7. 总结)

Rust闭包

闭包(Closure)是 Rust 中一个强大且灵活的特性,它允许你捕获环境中的变量并在稍后执行。Rust 的闭包设计既高效又安全,是函数式编程风格的重要组成部分。

Rust 的 闭包(closures)是可以保存进变量或作为参数传递给其他函数的匿名函数。

可以在一个地方创建闭包,然后在不同的上下文中执行闭包运算。

不同于函数,闭包允许捕获调用者作用域中的值。

1. 闭包基础

1.1 什么是闭包

闭包是一种可以捕获其环境的匿名函数。与普通函数不同,闭包可以访问定义它的作用域中的变量。

rust 复制代码
fn main() {
    let x = 4;
    
    // 定义一个闭包,捕获变量x
    let equal_to_x = |z| z == x;
    
    let y = 4;
    assert!(equal_to_x(y));
}

在这个例子中,闭包equal_to_x捕获了外部变量x,这是普通函数无法做到的。

1.2 闭包的基本语法

Rust闭包的基本语法如下:

两个竖线之间,是参数。竖线的后面是返回值,花括号里面是函数体,花括号可以省略

rust 复制代码
let closure_name = |parameters| -> return_type { body };

类型标注是可选的,Rust通常能推断出参数和返回值的类型:

rust 复制代码
// 完整形式
let add_one = |x: i32| -> i32 { x + 1 };

// 简化形式(类型推断)。但是不能推导多次不同的类型,同一个闭包函数只能推导出一次类型
let add_one = |x| x + 1;

1.3 闭包与函数的比较

闭包和普通函数的几个关键区别:

闭包使用 || 而不是()来包围参数

闭包可以省略类型标注(编译器通常能推断)

闭包可以捕获其环境中的变量

rust 复制代码
// 函数
fn function(x: i32) -> i32 { x + 1 }
// 闭包
let closure = |x| x + 1;

2. 闭包的捕获方式

当闭包从环境中捕获一个值,闭包会在闭包体中储存这个值以供使用。

这会使用内存并产生额外的开销,当执行不会捕获环境的更通用的代码场景中我们不希望有这些开销。

因为函数从未允许捕获环境,定义和使用函数也就从不会有这些额外开销。

闭包可以通过三种方式捕获其环境,他们直接对应函数的三种获取参数的方式:获取所有权,不可变借用和可变借用。

这三种捕获值的方式被编码为如下三个 Fn trait:

Fn:从其环境不可变的借用值 。可以多次调用,不能修改捕获的变量

FnMut:可变的借用值所以可以改变其环境。可以多次调用,可以修改捕获的变量

FnOnce:消费从周围作用域捕获的变量,闭包周围的作用域被称为其 环境,environment。

为了消费捕获到的变量,闭包必须获取其所有权并在定义闭包时将其移动进闭包。

其名称的 Once 部分代表了闭包不能多次获取相同变量的所有权,所以它只能被调用一次。

当创建一个闭包时,rust会根据其如何使用环境中的变量来推断我们希望如何引用环境。

由于所有闭包都可以被调用至少一次,因此所有闭包都实现了FnOnce。

没有移动被捕获变量的所有权到闭包的闭包函数也实现了FnMut。

而不需要对捕获的变量进行可变访问的闭包实现了Fn。

2.1 Fn:不可变借用

rust 复制代码
fn main() {
    //闭包的捕获
    //不可变借用
    let s = String::from("hello");

    let print_s = || {
        println!("{}", s); // 不可变借用s
    };

    print_s();
    println!("{}", s); // 可以再次使用s
}

2.2 FnMut:可变借用

rust 复制代码
fn main() {
    //可变借用
    let mut s = String::from("hello");
    let mut append_world = || {
        s.push_str(" world"); // 可变借用s
        println!("{}", s); // 可变借用s
    };
    append_world();
    println!("{}", s); // 这里可以再次使用s

}

2.3 FnOnce:获取所有权

rust 复制代码
fn main() {
    //获取所有权
    //获取所有权的闭包
    //将变量的所有权转移到闭包中,将变量返回
    let s = String::from("hello");
    // let consume_s = || {
    //     println!("{}", s);
    // std::mem::drop(s); // 获取s的所有权
    // };
    //对于非Copy类型的变量,闭包会捕获变量的所有权
    let consume_s = || s;

    consume_s();
    // println!("{}", s); // 错误!s的所有权已被移动

    let x = vec![1, 2, 3];
    let takes_ownership = || x; // 获取x的所有权

    let y = takes_ownership(); // 调用闭包,获取x的所有权
    println!("y = {:?}", y); // 可以使用y
    // 这里不能再使用x
    // println!("{:?}", x); // 错误!x的所有权已被移动

    //对于Copy类型的变量,闭包会捕获变量的不可变借用
    let x = 4;
    let get_number = || x; // 捕获x的不可变借用
    let y = get_number(); // 调用闭包,获取x的不可变借用
    println!("x = {}, y = {}", x, y); // 可以使用x和y


}

非Copy类型变量,闭包获取其所有权

Copy类型的变量,闭包会捕获变量的不可变借用

2.4 move关键字

使用move关键字强制闭包获取变量的所有权:

rust 复制代码
fn main() {
    // 闭包捕获变量的所有权
    // 使用move关键字强制闭包获取变量的所有权
    let print_s = move || {
        println!("{}", s);
    };

    print_s();
    println!("{}", s); // 错误!s的所有权已移动到闭包中
}

这在多线程编程中特别有用,可以确保数据安全地移动到新线程。

3. 闭包作为参数和返回值

3.1 闭包作为函数参数

rust 复制代码
//定义泛型函数,F可以是闭包函数
//对F进行泛型约束,必须实现Fn(i32)
fn apply<F>(f: F, x: i32) -> i32 where F: Fn(i32) -> i32 {
    //返回闭包函数的调用
    f(x)
}

fn main() {
    //创建闭包
    let double = |x| x * 2;
    //闭包作为参数传进去apply函数
    println!("{}", apply(double, 5)); // 输出10
}

3.2 闭包作为结构体字段

rust 复制代码
//闭包作为结构体字段
//泛型结构体,约束为闭包函数
//泛型闭包
struct Cacher<T> where T: Fn(i32) -> i32 {
    //calculation属于闭包函数,作为结构体的字段
    calculation: T,
    value: Option<i32>,
}

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

    //结构体方法
    fn value(&mut self, arg: i32) -> i32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.calculation)(arg);
                self.value = Some(v);
                v
            }
        }
    }
}

fn main() {
    let mut cacher = Cacher::new(|x| x * 2);

    let result1 = cacher.value(10);
    println!("Result for input 10: {}", result1);

    let result2 = cacher.value(10); // This should use the cached value
    println!("Result for input 10 (cached): {}", result2);

    let result3 = cacher.value(20); // This will compute a new value
    println!("Result for input 20: {}", result3);
}

3.3 闭包作为函数返回值

也可以返回闭包,因为闭包的大小在编译时未知,需要使用 trait 对象或 impl Trait 语法:

rust 复制代码
//闭包作为返回值
//注意语法格式
//返回一个闭包函数
fn returns_closure() -> impl Fn(i32) -> i32 {
    |x| x + 1
}
fn main() {
    let closure = returns_closure();
    println!("closure: {}", closure(1));
}

或者使用 Box:

rust 复制代码
// 闭包作为返回值
// 注意语法格式
// 返回一个闭包函数
fn returns_closure() -> impl Fn(i32) -> i32 {
    |x| x + 1
}

// 使用box
// 使用box有性能消耗
fn returns_closure_box() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

fn main() {
    let closure = returns_closure();
    println!("closure: {}", closure(1));

    let closure_box = returns_closure_box();
    println!("closure_box: {}", closure_box(1));
}

4. 闭包的实际应用案例

4.1 缓存/记忆化模式

使用闭包实现缓存:

rust 复制代码
//使用闭包做个缓存系统
//闭包作为结构体字段
//泛型结构体,约束为闭包函数
//泛型闭包
struct Cacher<T> where T: Fn(i32) -> i32 {
    //calculation属于闭包函数,作为结构体的字段
    calculation: T,
    value: Option<i32>,
}

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

    //结构体方法
    fn value(&mut self, arg: i32) -> i32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.calculation)(arg);
                self.value = Some(v);
                v
            }
        }
    }
}

fn main() {
    let mut cacher = Cacher::new(|x| x * 2);

    let result1 = cacher.value(10);
    println!("Result for input 10: {}", result1);

    let result2 = cacher.value(10); // This should use the cached value
    println!("Result for input 10 (cached): {}", result2);

    let result3 = cacher.value(20); // This will compute a new value
    println!("Result for input 20: {}", result3);
}

5. 高级闭包技巧

5.1 闭包与生命周期

当闭包捕获引用时,需要考虑生命周期:

rust 复制代码
//闭包中的生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("short");
        let closure = || longest(&string1, &string2);
        result = closure();
    }
    println!("The longest string is {}", result);
}

上面的代码会编译失败,因为string2的生命周期不够长。

解决方案是让闭包只返回string1:

rust 复制代码
//闭包中的生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("short");
        let closure = || &string1; // 只捕获string1
        result = closure();
    }
    println!("The longest string is {}", result);
}

6. 性能考虑

闭包在Rust中的性能与普通函数相当,因为:

闭包不进行堆分配,没有运行时开销(不像其他语言的闭包可能需要在堆上分配,除非使用Box)

编译器可以内联闭包调用

捕获环境的闭包通常会被编译器优化

7. 总结

Rust的闭包是一个强大而灵活的特性,它:

可以捕获环境中的变量

有三种捕获方式(Fn、FnMut、FnOnce)

性能与普通函数相当

广泛应用于迭代器、线程、回调等场景

可以与泛型、trait对象等Rust特性结合使用

掌握闭包的使用是成为Rust高级程序员的重要一步。通过本文的示例和实践,相信大家伙应该已经对Rust闭包有了深入的理解。

在实际开发中,多思考何时使用闭包能让代码更简洁、更富有表达力,同时也要注意闭包捕获变量的生命周期和所有权问题。

相关推荐
开源技术12 分钟前
如何将本地LLM模型与Ollama和Python集成
开发语言·python
Hello World . .17 分钟前
数据结构:队列
c语言·开发语言·数据结构·vim
clever10129 分钟前
在QtCreator 4.10.2中调试qt程序qDebug()输出中文为乱码问题的解决
开发语言·qt
测试开发Kevin1 小时前
小tip:换行符CRLF 和 LF 的区别以及二者在实际项目中的影响
java·开发语言·python
松☆1 小时前
Dart 核心语法精讲:从空安全到流程控制(3)
android·java·开发语言
编码者卢布2 小时前
【Azure Storage Account】Azure Table Storage 跨区批量迁移方案
后端·python·flask
编码者卢布2 小时前
【App Service】Java应用上传文件功能部署在App Service Windows上报错 413 Payload Too Large
java·开发语言·windows
kaikaile19952 小时前
结构风荷载理论与Matlab计算
开发语言·matlab
切糕师学AI2 小时前
ARM 汇编器中的伪指令(Assembler Directives)
开发语言·arm开发·c#
吕司2 小时前
Qt的信号与槽
开发语言·qt