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

06-02 1043阅读

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

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

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

🏆 作者简介:景天科技苑

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

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

所属的专栏:Rust语言通关之路

景天的主页:景天科技苑

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

文章目录

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

                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
                }
                

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

                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
                }
                

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

                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类型变量,闭包获取其所有权

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

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

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

                2.4 move关键字

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

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

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

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

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

                3.1 闭包作为函数参数

                //定义泛型函数,F可以是闭包函数
                //对F进行泛型约束,必须实现Fn(i32)
                fn apply(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
                }
                

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

                3.2 闭包作为结构体字段

                //闭包作为结构体字段
                //泛型结构体,约束为闭包函数
                //泛型闭包
                struct Cacher where T: Fn(i32) -> i32 {
                    //calculation属于闭包函数,作为结构体的字段
                    calculation: T,
                    value: Option,
                }
                impl Cacher where T: Fn(i32) -> i32 {
                    fn new(calculation: T) -> Cacher {
                        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);
                }
                

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

                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));
                }
                

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

                或者使用 Box:

                // 闭包作为返回值
                // 注意语法格式
                // 返回一个闭包函数
                fn returns_closure() -> impl Fn(i32) -> i32 {
                    |x| x + 1
                }
                // 使用box
                // 使用box有性能消耗
                fn returns_closure_box() -> Box 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));
                }
                

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

                4. 闭包的实际应用案例

                4.1 缓存/记忆化模式

                使用闭包实现缓存:

                //使用闭包做个缓存系统
                //闭包作为结构体字段
                //泛型结构体,约束为闭包函数
                //泛型闭包
                struct Cacher where T: Fn(i32) -> i32 {
                    //calculation属于闭包函数,作为结构体的字段
                    calculation: T,
                    value: Option,
                }
                impl Cacher where T: Fn(i32) -> i32 {
                    fn new(calculation: T) -> Cacher {
                        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);
                }
                

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

                5. 高级闭包技巧

                5.1 闭包与生命周期

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

                //闭包中的生命周期
                fn longest
                    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的生命周期不够长。

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

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

                //闭包中的生命周期
                fn longest
                    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);
                }
                

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

                6. 性能考虑

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

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

                编译器可以内联闭包调用

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

                7. 总结

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

                可以捕获环境中的变量

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

                性能与普通函数相当

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

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

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

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

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码