what is closure?
Rust 里的 closure 是一种匿名函数,可以保存在变量里用于日后的调用,也可以作为参数传递给函数。而且相比于函数,closure 可以在其定义域内捕获变量。
closure 的类型推导
- closure 不是泛型,因此当编译器推导出一种类型后,它就会一直使用该类型。
1 | let example_closure = |x| x; |
Rust 闭包可以用泛型吗?
1 | struct Cacher<T, E> |
closure 与内存
当闭包从环境中捕获一个值时,会分配内存去存储这些值。对于有些场景来说,这种额外的内存分配会成为一种负担。与之相比,函数就不会去捕获这些环境值,因此定义和使用函数不会拥有这种内存负担。
三种闭包特征 (Trait)
闭包捕获变量有三种途径,恰好对应函数参数的三种传入方式:转移所有权、可变借用、不可变借用,因此相应的 Fn 特征也有三种:
这三种的关系并不是说,我定义了闭包类型满足 FnOnce
那么这个闭包就只能调用一次,而是会根据捕获和使用方式,自动推导闭包属于哪一种 Trait
- 可以移动变量所有权
- 只能调用一次(除非也实现
Copy
trait)
来看这么一个例子
1 | fn fn_once<F>(func: F) |
这里一个问题是,FnOnce
特征的 func
变量为什么只能被调用一次?怎么从所有权的角度进行解释?
从捕获的变量的角度来说,闭包捕获了变量的所有权,根据 Rust 的语言设计,所有权只能在一个人手里,于是第二次再调用闭包就会无法拿到所有权。
那么闭包自身的所有权转移给谁了呢?闭包 func
的所有权会转移到调用闭包的代码上下文。此时,闭包可能释放其捕获的资源(如 x
),或者将这些资源的所有权转移给其他逻辑(即使没有显式转移,闭包本身的调用也意味着其自身被“销毁”)
这里
func(4)
的报错其实与fn_once(|z| z == x.len())
这个匿名函数本身没关系,只是泛型函数自己做的类型检查。
- 使用变量的可变借用 (
&mut
) - 闭包变量本身也需要定义为
let mut
,或者作为函数参数时以mut
的方式借用
- 使用变量的不可变借用
move
与 Fn Trait
- 实际上使用了
move
的闭包依然可以使用Fn
或FnMut
特征 - 一个闭包实现了哪种
Fn
特征取决于该闭包如何使用被捕获的变量,而不是取决于闭包如何捕获它们
这个点怎么理解呢?考虑下面的代码
1 let f = move || println!("{}", s.len());这里
f
闭包同时实现了FnOnce, FnMut, Fn
,尽管move
把所有权都转移走了(“闭包如何捕获他们”)但是s.len()
仅仅只使用了不可变借用(“该闭包如何使用被捕获的变量”),因此还是Fn
,也因此可以作为FnOnce, FnMut
泛型的参数。
更具体的,一个闭包并不仅仅实现某一种 Fn
特征,规则如下:
- 所有的闭包都自动实现了
FnOnce
特征,因此任何一个闭包都至少可以被调用一次 - 没有移出所捕获变量的所有权的闭包自动实现了
FnMut
特征 - 不需要对捕获变量进行改变的闭包自动实现了
Fn
特征
我们可以看成是一个继承的关系:
通过源码可以看得更清晰:
1 | pub trait FnOnce<Args: Tuple> { |