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

在这里插入图片描述

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

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

🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Rust开发,Python全栈,Golang开发,云原生开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

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

在这里插入图片描述

Rust闭包

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

1. 闭包基础

1.1 什么是闭包

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

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闭包的基本语法如下:
两个竖线之间,是参数。竖线的后面是返回值,花括号里面是函数体,花括号可以省略

let closure_name = |parameters| -> return_type { body };

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

// 完整形式
let add_one = |x: i32| -> i32 { x + 1 };

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

1.3 闭包与函数的比较

闭包和普通函数的几个关键区别:
闭包使用 || 而不是()来包围参数
闭包可以省略类型标注(编译器通常能推断)
闭包可以捕获其环境中的变量

// 函数
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:不可变借用

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

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

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

在这里插入图片描述

2.2 FnMut:可变借用

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:获取所有权

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关键字强制闭包获取变量的所有权:

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

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

在这里插入图片描述

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

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

3.1 闭包作为函数参数

//定义泛型函数,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 闭包作为结构体字段

//闭包作为结构体字段
//泛型结构体,约束为闭包函数
//泛型闭包
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 语法:

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

在这里插入图片描述

或者使用 Box:

// 闭包作为返回值
// 注意语法格式
// 返回一个闭包函数
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 缓存/记忆化模式

使用闭包实现缓存:

//使用闭包做个缓存系统
//闭包作为结构体字段
//泛型结构体,约束为闭包函数
//泛型闭包
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 闭包与生命周期

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

//闭包中的生命周期
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:

//闭包中的生命周期
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闭包有了深入的理解。
在实际开发中,多思考何时使用闭包能让代码更简洁、更富有表达力,同时也要注意闭包捕获变量的生命周期和所有权问题。

评论 42
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

景天科技苑

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

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

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

打赏作者

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

抵扣说明:

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

余额充值