语法

Closure看上去是这样的:

    let plus_one = |x: i32| x + 1;
assert_eq!(2, plus_one(1));

首先创建一个绑定plus_one,然后将它分配给一个closure,body是一个expression,注意{ } 也是一个expression。

它也可以被写成这样:

    let plus_two = |x| {
let mut result: i32 = x; result += 1;
result += 1; result
};
assert_eq!(4, plus_two(2));

和常规的函数定义相比,区别就是closure没有使用关键词 fn ,区分一下:

fn  plus_one_v1   (x: i32) -> i32 { x + 1 }
let plus_one_v2 = |x: i32| -> i32 { x + 1 };
let plus_one_v3 = |x: i32| x + 1 ;

值得注意的是在closure中参数和返回值的类型都是可以省略的,下面这种形式也是可以的:

let plus_one = |x| x + 1;

闭包和它的环境

一个小例子:

    let num = 5;
let plus_num = |x: i32| x + num; assert_eq!(10, plus_num(5));

也就是说,plus_num引用了一个在它作用于中的变量num,具体地说这是一个borrow,它满足所有权系统的要求,来看一个错误的例子:

let mut num = 5;
let plus_num = |x: i32| x + num; let y = &mut num; error: cannot borrow `num` as mutable because it is also borrowed as immutable
let y = &mut num;
^~~

在上面的代码中,plus_num已经对num做了不可变引用,而在plus_one的作用域内,又发生了一次可变引用,所以就违反了所有权系统中的如下规则:

如果对一个绑定进行了不可变引用,那么在该引用未超出作用域之前,不可以再进行可变引用,反之也是一样。

对代码做出如下修改即可:

    let mut num = 5;
{
let plus_num = |x: i32| x + num; } // plus_num goes out of scope, borrow of num ends let y = &mut num;

再看一个例子:

    let nums = vec![1, 2, 3];
let takes_nums = || nums;
println!("{:?}", nums);

有问题吗?
有,而且是大问题,编译器的报错如下:


closure.rs:8:19: 8:23 error: use of moved value: `nums` [E0382]
closure.rs:8 println!("{:?}", nums);

从错误中可以看出来,在最后一个输出语句中,nums已经没有对资源 vec![1, 2, 3] 的 所有权了,该资源的所有权已经被move到了closure中去了。

那么问题来了:

为什么在前面的例子中closure是borrow,而到了这里就变成了move了呢?

我们从头梳理一遍:

    let mut num = 5;
let plus_num = || num + 1;
let num2 = &mut num;
Error:
closure.rs:5:21: 5:24 error: cannot borrow `num` as mutable because it is also borrowed as immutable
closure.rs:5 let num2 = &mut num;

说明在closure中发生了immutable borrow,这样才会和下面的&mut冲突,现在我们来做一个改动:

    let plus_num = || num + 1;
// 改成如下语句
let mut plue_num = || num += 1;

再编译一次:

Error:
closure.rs:4:17: 4:20 error: cannot borrow `num` as mutable more than once at a time
closure.rs:4 let num2 = &mut num;

可以发现,在closure中发生了mutable borrow,为什么会这样呢?

在closure无非就是这3种情况:

  • by reference: &T

  • by mutable reference: &mut T

  • by value: T

    至于是这3个中的哪一个,取决于你closure内部怎么用,然后编译器自动推断绑定的类型是Fn() FnMut() 还是FnOnce()

    let plus_num = || num + 1;         // 这个只需要引用即可,所以plus_num类型为Fn()
let mut plue_num = || num += 1; // 这个则需要&mut T,所以plus_num类型为FnMut()
// 这是手册里的一个例子
// 这是一个没有实现Copy trait的类型
let movable = Box::new(3);
// `drop` 需要类型T,所以closure环境就需要 by value T.,所以consume类型为FnOnce()
let consume = || {
drop(movable); // 这里发生了move
};
// 所以这个consume只能执行一次
consume();

有一点要注意的是:
在前面的例子应该分成两类:

  1. let a= 100i32;

  2. let a = vec![1,2,3];

区别就是i32类型实现了copy trait,而vector没有!!!

参考:http://rustbyexample.com/fn/closures/capture.html

Move closure

使用move关键字,强制closure获得所有权,但下面的例子得注意一下:

    let num = 5;
let owns_num = move |x: i32| x + num;

尽管这里使用move,变量遵循move语义,但是,在这里5实现了Copy,所以owns_own获得的是 5 的拷贝的所有权,有什么区别呢?
来看看这段代码:

    let mut num = 5;
{
let mut add_num = |x: i32| num += x;
add_num(5);
}
assert_eq!(10, num);

这段代码得到的是我们想要的结果,但是如果我们加上move关键字呢?上面的代码就会报错,因为num的值仍是 5 ,并没有发生改变,

为什么呢?
上面说到了,move强制闭包环境获得所有权,但是 5 实现了Copy,所以闭包获得的是其拷贝的所有权,同理闭包中修改的也是 5 的拷贝。

总结

在Rust中闭包的概念并不好理解,因为牵扯到了太多所有权的概念,可以先把所有权弄懂了,闭包也就好理解了。

 

Rust基础笔记:闭包的更多相关文章

  1. JavaScript基础笔记一

    一.真假判断 真的:true.非零数字.非空字符串.非空对象 假的:false.数字零.空字符串.空对象.undefined 例: if(0){ alert(1) }else{ alert(2) } ...

  2. Java基础笔记 – Annotation注解的介绍和使用 自定义注解

    Java基础笔记 – Annotation注解的介绍和使用 自定义注解 本文由arthinking发表于5年前 | Java基础 | 评论数 7 |  被围观 25,969 views+ 1.Anno ...

  3. php代码审计基础笔记

    出处: 九零SEC连接:http://forum.90sec.org/forum.php?mod=viewthread&tid=8059 --------------------------- ...

  4. MYSQL基础笔记(六)- 数据类型一

    数据类型(列类型) 所谓数据烈性,就是对数据进行统一的分类.从系统角度出发时为了能够使用统一的方式进行管理,更好的利用有限的空间. SQL中讲数据类型分成三大类:1.数值类型,2.字符串类型和时间日期 ...

  5. MYSQL基础笔记(五)- 练习作业:站点统计练习

    作业:站点统计 1.将用户的访问信息记录到文件中,独占一行,记录IP地址 <?php //站点统计 header('Content-type:text/html;charset=utf-8'); ...

  6. MYSQL基础笔记(四)-数据基本操作

    数据操作 新增数据:两种方案. 1.方案一,给全表字段插入数据,不需要指定字段列表,要求数据的值出现的顺序必须与表中设计的字段出现的顺序一致.凡是非数值数据,到需要使用引号(建议使用单引号)包裹. i ...

  7. MYSQL基础笔记(三)-表操作基础

    数据表的操作 表与字段是密不可分的. 新增数据表 Create table [if not exists] 表名( 字段名 数据类型, 字段名 数据类型, 字段n 数据类型 --最后一行不需要加逗号 ...

  8. MYSQL基础笔记(二)-SQL基本操作

    SQL基本操作 基本操作:CRUD,增删改查 将SQL的基本操作根据操作对象进行分类: 1.库操作 2.表操作 3.数据操作 库操作: 对数据库的增删改查 新增数据库: 基本语法: Create da ...

  9. MYSQL基础笔记(一)

    关系型数据库概念: 1.什么是关系型数据库? 关系型数据库:是一种建立在关系模型(数学模型)上的数据库 关系模型:一种所谓建立在关系上的模型. 关系模型包含三个方面: 1.数据结构:数据存储的问题,二 ...

随机推荐

  1. Java类与类之间的6种关系及uml表示

    一.继承关系 继承指的是一个类(称为子类.子接口)继承另外的一个类(称为父类.父接口)的功能,并可以增加它自己的新功能的能力.在Java中继承关系通过关键字extends明确标识,在设计时一般没有争议 ...

  2. NodeJS开发博客(三) 数据的保存

    什么是cookie 存储在浏览器的一段字符串(最大5k) 跨域不共享 格式如 k1=v1 k2=v2 因此可以存储结构化数据 每次发送http请求,会将请求域的cookie一起发送给server se ...

  3. 常见的HTML5语义化标签

    ​ <title>:页面主体内容.<hn>:h1~h6,分级标题,<h1> 与 <title> 协调有利于搜索引擎优化.<ul>:无序列表. ...

  4. 服务器nginx部署PHP项目样式不出来要注意的小问题

    服务器使用nginx部署PHP项目的时候如果样式没有 出来,那么很可能 location 块里出问题了. 比如 location / { root /home/wwwroot/default/php_ ...

  5. splay树 1285 宠物收养所

    #include<cstdio> #include<iostream> using namespace std; int shu[80004][2],n,size,root,k ...

  6. PHP sha1()函数

    <!DOCTYPE html> <html> <body> <?php $str = "dashu"; echo sha1($str); ...

  7. P1850 换教室——期望DP

    题目描述 对于刚上大学的牛牛来说,他面临的第一个问题是如何根据实际情况申请合适的课程. 在可以选择的课程中,有 2n2n2n 节课程安排在 nnn 个时间段上.在第 iii(1≤i≤n1 \leq i ...

  8. P2698 [USACO12MAR]花盆Flowerpot——单调队列

    记录每天看(抄)题解的日常: https://www.luogu.org/problem/P2698 我们可以把坐标按照x递增的顺序排个序,这样我们就只剩下纵坐标了: 如果横坐标(l,r)区间,纵坐标 ...

  9. vue文件中提示Expected Boolean, got String

    这种情况是有一些属性的值应该填写Boolean类型,但是当前的值可能是“”--字符串 这种情况只需要在属性前面加上:即可. eg:

  10. python3编程基础之一:代码封装

    几乎现代的编程语言都支持函数,函数是代码段的封装,并能实现一特定功能,并能重复使用的代码单位.之前的pow()和sqrt()和print()和input()等类似的内置函数,就是python内部已经实 ...