rust闭包的使用

 更新时间:2023年12月07日 14:47:30   作者:int8  
闭包在Rust中是非常强大的功能,允许你编写更灵活和表达性的代码,本文主要介绍了rust闭包的使用,具有一定的参考价值,感兴趣的可以了解一下
(福利推荐:【腾讯云】服务器最新限时优惠活动,云服务器1核2G仅99元/年、2核4G仅768元/3年,立即抢购>>>:9i0i.cn/qcloud

(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅2998元/3年,立即抢购>>>:9i0i.cn/aliyun

一、闭包是什么

(一)闭包是什么

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

function f1(){
	var n=999;
	function f2(){
		alert(n);
	}
}

在上面的代码中,函数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

上一节代码中的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()());

代码片段二。

var name = "The Window";
var object = {
	name : "My Object",
	getNameFunc : function(){
		var that = this;
		return function(){
			return that.name;
		};
	}
};
alert(object.getNameFunc()());

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

二、rust闭包

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

(一)定义闭包

它的定义语法如下

|parameter| {
     // 闭包的具体逻辑
}

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

|x: u32| -> u32 { x + 1 }
|x|             { x + 1 }
|x|               x + 1  

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

let closure_function = |parameter| {
     // 闭包的具体逻辑
}

(二)使用闭包

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

closure_function(parameter);

范例:

fn main(){
     let is_even = |x| {
         x%2==0
     };
     let no = 13;
     println!("{} is even ? {}",no, is_even(no));
}
编译运行结果如下
13 is even ? false

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

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

所有的闭包都实现了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));
}

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

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

struct SetVec {
     set: HashSet<u32>,
     vec: Vec<u32>
}
impl SetVec {
     fn populate(&mut self) {
         let vec = &mut self.vec;
         self.set.iter().for_each(|&n| {
             vec.push(n);
         })
     }
}

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

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

fn f1(x: impl Fn()){
    x();
}
fn f2<F>(x:F)
where
F:Fn()
{
     x();
}
fn main() {
     f1(||{});
     f2(||{});
}

(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
}

到此这篇关于rust闭包的应用场景的文章就介绍到这了,更多相关rust闭包内容请搜索程序员之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持程序员之家! 

相关文章

  • Rust?Atomics?and?Locks内存序Memory?Ordering详解

    Rust?Atomics?and?Locks内存序Memory?Ordering详解

    这篇文章主要为大家介绍了Rust?Atomics?and?Locks内存序Memory?Ordering详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-02-02
  • Rust使用Sled添加高性能嵌入式数据库

    Rust使用Sled添加高性能嵌入式数据库

    这篇文章主要为大家详细介绍了如何在Rust项目中使用Sled库,一个为Rust生态设计的现代、高性能嵌入式数据库,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-03-03
  • 为什么要使用 Rust 语言、Rust 语言有什么优势

    为什么要使用 Rust 语言、Rust 语言有什么优势

    虽然 Rust 是一种通用的多范式语言,但它的目标是 C 和 C++占主导地位的系统编程领域,很多朋友会问rust语言难学吗?rust语言可以做什么,今天带着这些疑问通过本文详细介绍下,感兴趣的朋友一起看看吧
    2022-10-10
  • rust?中生成与使用protobuf的方法

    rust?中生成与使用protobuf的方法

    这篇文章主要介绍了rust中protobuf生成与使用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-05-05
  • Rust如何进行模块化开发技巧分享

    Rust如何进行模块化开发技巧分享

    Rust模块化,模块化有助于代码的管理和层次逻辑的清晰,本文主要介绍了Rust如何进行模块化开发,结合实例代码给大家讲解的非常详细,需要的朋友可以参考下
    2023-01-01
  • 如何用Rust打印hello world

    如何用Rust打印hello world

    这篇文章主要介绍了如何用Rust打印hello world,本文分步骤通过图文并茂的形式给大家讲解的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-09-09
  • 如何使用rust实现简单的单链表

    如何使用rust实现简单的单链表

    实现单链表在别的语言里面可能是一件简单的事情,单对于Rust来说,绝对不简单,下面这篇文章主要给大家介绍了关于如何使用rust实现简单的单链表的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-03-03
  • Rust生成随机数的项目实践

    Rust生成随机数的项目实践

    Rust标准库中并没有随机数生成器,常见的解决方案是使用rand包,本文主要介绍了Rust生成随机数的项目实践,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • Rust中GUI库egui的简单应用指南

    Rust中GUI库egui的简单应用指南

    egui(发音为“e-gooey”)是一个简单、快速且高度可移植的 Rust 即时模式 GUI 库,跨平台、Rust原生,适合一些小工具和游戏引擎GUI,下面就跟随小编一起来看看它的具体使用吧
    2024-03-03
  • Rust中箱、包和模块的学习笔记

    Rust中箱、包和模块的学习笔记

    Rust中有三个重要的组织概念:箱、包、模块,本文主要介绍了Rust中箱、包和模块的学习笔记,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2024-03-03

最新评论

?


http://www.vxiaotou.com