• rust闭包


    一、闭包是什么

    (一)闭包是什么
    我们先来看看javascript中的闭包。
    在函数外部无法读取函数内的局部变量。但是我们有时候需要得到函数内的局部变量,那么如何从外部读取局部变量?那就是在函数的内部,再定义一个函数。

    function f1(){
    	var n=999;
    	function f2(){
    		alert(n);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在上面的代码中,函数f2在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是"链式作用域",子作用域会一级一级地向上寻找所有父作用域的变量。
    既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!

    function f1(){
    	var n=999;
    	function f2(){
    		alert(n);
    	}
    	return f2;
    }
    var result=f1();//实际上f1()执行完之后,并没有释放内存,n还在
    result(); // 999。执行f2(),能访问f1中的n
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    上一节代码中的f2函数,就是闭包。
    各种专业文献上的"闭包"定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。
    由于只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
    所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

    闭包可以读取函数内部的变量,可以让这些变量的值始终保持在内存中。
    f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制回收。

    (二)闭包的优缺点
    优点:
    直接访问父作用域中的局部变量,避免了传参的问题
    逻辑连续,避免脱离当前逻辑,在外部编写代码
    缺点:
    因为使用闭包,可以使函数在执行完后不被销毁,保留在内存中,如果大量使用闭包就会造成内存泄露,内存消耗很大

    (三)使用场景
    (1)设置timer
    (2)用后即弃的一次性功能,没必要另外写个函数

    (四)思考题
    如果你能理解下面两段代码的运行结果,应该就算理解闭包的运行机制了。
    代码片段一。

    var name = "The Window";
    var object = {
    	name : "My Object",
    	getNameFunc : function(){
    		return function(){
    			return this.name;
    		};
    	}
    };
    alert(object.getNameFunc()());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    代码片段二。

    var name = "The Window";
    var object = {
    	name : "My Object",
    	getNameFunc : function(){
    		var that = this;
    		return function(){
    			return that.name;
    		};
    	}
    };
    alert(object.getNameFunc()());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    第一个 打印结果为The window
    第二个 打印结果为My Object
    this是由它所在函数调用时的环境决定的,而不是由它所在函数定义的环境决定的。
    第一个this是在调用闭包时确定的,环境是全局环境
    第二个this是在调用getNameFunc时确定的,环境是object内

    二、rust闭包

    rust闭包,跟javascript闭包原理基本一样。就语法格式不一样。
    rust闭包没有名字,包含于一个函数内。
    所以可以直接认为rust闭包是一个没有函数名的内联函数。

    (一)定义闭包
    它的定义语法如下

    |parameter| {
         // 闭包的具体逻辑
    }
    
    • 1
    • 2
    • 3

    闭包不要求在参数和返回值上注明类型
    例子

    |x: u32| -> u32 { x + 1 }
    |x|             { x + 1 }
    |x|               x + 1  
    
    • 1
    • 2
    • 3

    闭包虽然没有名称,但我们可以将闭包赋值给一个变量

    let closure_function = |parameter| {
         // 闭包的具体逻辑
    }
    
    • 1
    • 2
    • 3

    (二)使用闭包
    1.使用小括号 () 来调用闭包

    closure_function(parameter);
    
    • 1

    范例:

    fn main(){
         let is_even = |x| {
             x%2==0
         };
         let no = 13;
         println!("{} is even ? {}",no, is_even(no));
    }
    编译运行结果如下
    13 is even ? false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.直接访问父作用域中的变量
    也叫捕获变量。闭包周围的作用域称为环境
    不必通过传参的方式,而是直接访问环境中的变量
    范例:

    fn main(){
         let val = 10;
         // 访问外层作用域变量val
         let closure2 = |x| {
             x + val // 内联函数访问外层作用域变量
         };
         println!("{}", closure2(2));
    }
    编译运行结果如下
    12
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    所有的闭包都实现了Fn、FnMut、FnOnce特性中的一个。
    闭包有三种捕获方式,Rust会根据捕获方式来决定它们实现的trait。
    (1)获取所有权
    使用这种方式的闭包实现了FnOnce特性。
    闭包获取其所有权并在定义闭包时将其移动进闭包。Once代表了闭包不能多次获取相同变量的所有权,所以它只能被调用一次。

    使用这种方式,要在开头添加move关键字。这种方式用于允许闭包比其捕获的变量活得更久,例如返回闭包或生成新线程。
    例子

    fn main() {
         let x = vec![1, 2, 3];
         let equal_to_x = move |z| z == x;
         println!("can't use x here: {:?}", x);
         let y = vec![1, 2, 3];
         assert!(equal_to_x(y));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    x被移动进了闭包,因为闭包使用move关键字定义。接着闭包获取了x的所有权,同时main就不再允许在println! 语句中使用x了。去掉println! 即可修复问题。

    (2)可变借用
    使用这种方式的闭包实现了FnMut特性。
    因为是可变引用,所以可以改变其环境。

    (3)不可变借用
    使用这种方式的闭包实现了Fn特性。
    因为是不可变引用,所以不可修改其环境。

    例子

    fn main() {
         let x = 5;
         let y = 10;
         // Fn闭包:通过不可变引用捕获变量
         let add = |a| a + x;
         // FnMut闭包:通过可变引用捕获变量
         let mut multiply = |a| {
             x * y * a
         };
         // FnOnce闭包:通过值捕获变量
         let divide = move |a| {
             a / y
         };
         let result1 = add(3);
         let result2 = multiply(2);
         let result3 = divide(10);
         println!("The results are: {}, {}, {}", result1, result2, result3);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    复合类型(如结构体)始终是全部捕获的,而不是各个字段分开捕获的。如果真要捕获单个字段,那可能需要先借用该字段到本地局部变量中:

    struct SetVec {
         set: HashSet,
         vec: Vec
    }
    impl SetVec {
         fn populate(&mut self) {
             let vec = &mut self.vec;
             self.set.iter().for_each(|&n| {
                 vec.push(n);
             })
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    相反,如果闭包直接使用了self.vec,那么它将尝试通过可变引用捕获self。但是因为self.set已经被借出用来迭代了,所以代码将无法编译。

    3.闭包作为参数和返回值
    闭包可以作为函数的参数和返回值,如此使用时,必须指定类型,类型就是上面讲的Fn、FnMut、FnOnce。
    (1)作为参数

    fn f1(x: impl Fn()){
        x();
    }
    fn f2(x:F)
    where
    F:Fn()
    {
         x();
    }
    fn main() {
         f1(||{});
         f2(||{});
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (2)作为返回值

    fn f1() -> impl Fn(i32) {
         |x|{println!("{}",x)}
    }
    fn f2() -> impl Fn(i32) {
         let a=100;
         move |x|{println!("{}",x+a)} //如果使用引用方式捕获,那么返回后a销毁,引用会变成悬垂引用,所以编译器不会通过
    }
    fn main() {
         f1()(9);//9
         f2()(9);//109
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  • 相关阅读:
    滴滴6月或发布造车计划;英特尔顶级专家Mike Burrows跳槽AMD;Android 13开发者预览版2发布|极客头条
    长连接和短连接
    如何集成验证码短信API到你的应用程序
    URDF+Gazebo+Rviz仿真
    Selenium打开页面,出现弹窗需要登录账号密码,怎么解决?
    ovirt:api接口+keystone接口+neutron接口示例
    安装delphi 10.4 社区版
    33-Java循环综合练习:逢7过、求平方根...
    基于SSM的家政服务网站
    第104天: 权限提升-Linux 系统&环境变量&定时任务&权限配置不当&MDUT 自动化
  • 原文地址:https://blog.csdn.net/inxunxun/article/details/133691252