【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闭包有了深入的理解。

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

相关推荐
郭尘帅66634 分钟前
Vue3中实现轮播图
开发语言·前端·javascript
豌豆花下猫40 分钟前
Python 潮流周刊#102:微软裁员 Faster CPython 团队(摘要)
后端·python·ai
秋野酱1 小时前
基于javaweb的SpringBoot驾校预约学习系统设计与实现(源码+文档+部署讲解)
spring boot·后端·学习
Thomas_YXQ1 小时前
Unity3D Overdraw性能优化详解
开发语言·人工智能·性能优化·unity3d
北辰浮光1 小时前
[springboot]SSM日期数据转换易见问题
java·spring boot·后端
lanbing1 小时前
PHP 与 面向对象编程(OOP)
开发语言·php·面向对象
yzx9910131 小时前
Gensim 是一个专为 Python 设计的开源库
开发语言·python·开源
木梓辛铭1 小时前
Spring Cache的详细使用
java·后端·spring
麻雀无能为力1 小时前
python自学笔记2 数据类型
开发语言·笔记·python
招风的黑耳2 小时前
Java集合框架详解与使用场景示例
java·开发语言