what is closure?
Rust 里的 closure 是一种匿名函数 ,可以保存在变量里用于日后的调用,也可以作为参数传递给函数。而且相比于函数,closure 可以在其定义域内捕获变量 。
closure 的类型推导
closure 不是泛型,因此当编译器推导出一种类型后,它就会一直使用该类型 。
1 2 3 4 5 6 let example_closure = |x| x;let s = example_closure (String ::from ("hello" ));let n = example_closure (5 );
Rust 闭包可以用泛型吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 struct Cacher <T, E>where T: Fn (E) -> E, E: Clone , { query: T, value: Option <E>, } impl <T, E> Cacher<T, E>where T: Fn (E) -> E, E: Clone , { fn new (query: T) -> Cacher<T, E> { Cacher { query, value: None } } fn value (&mut self , arg: E) -> E { match self .value { Some (ref v) => v.clone (), None => { let v = (self .query)(arg.clone ()); self .value = Some (v.clone ()); v.clone () } } } } fn main () { let mut test = Cacher::new (|d: String | d + " world" ); println! ("first cache: {}" , test.value ("wtf" .to_string ())); println! ("second cache: {}" , test.value ("hello" .to_string ())); }
closure 与内存
当闭包从环境中捕获一个值时,会分配内存去存储这些值。对于有些场景来说,这种额外的内存分配会成为一种负担。与之相比,函数就不会去捕获这些环境值,因此定义和使用函数不会拥有这种内存负担。
三种闭包特征 (Trait)
闭包捕获变量有三种途径,恰好对应函数参数的三种传入方式:转移所有权、可变借用、不可变借用,因此相应的 Fn 特征也有三种:
这三种的关系并不是说,我定义了闭包类型满足 FnOnce 那么这个闭包就只能调用一次,而是会根据捕获和使用方式,自动推导闭包属于哪一种 Trait
可以移动变量所有权
只能调用一次(除非也实现 Copy trait)
来看这么一个例子
1 2 3 4 5 6 7 8 9 10 11 fn fn_once <F>(func: F)where F: FnOnce (usize ) -> bool ,{ println! ("{}" , func (3 )); println! ("{}" , func (4 )); } fn main () { let x = vec! [1 , 2 , 3 ]; fn_once (|z| {z == x.len ()}) }
这里一个问题是,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 特征
我们可以看成是一个继承的关系:
FnOnce ⟶ FnMut ⟶ Fn
\boxed{\texttt{FnOnce}}\longrightarrow\boxed{\texttt{FnMut}}\longrightarrow\boxed{\texttt{Fn}}
FnOnce ⟶ FnMut ⟶ Fn 通过源码可以看得更清晰:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 pub trait FnOnce <Args: Tuple> { #[lang = "fn_once_output" ] #[stable(feature = "fn_once_output" , since = "1.12.0" )] type Output ; #[unstable(feature = "fn_traits" , issue = "29625" )] extern "rust-call" fn call_once (self , args: Args) -> Self ::Output; } pub trait FnMut <Args: Tuple>: FnOnce <Args> { #[unstable(feature = "fn_traits" , issue = "29625" )] extern "rust-call" fn call_mut (&mut self , args: Args) -> Self ::Output; } pub trait Fn <Args: Tuple>: FnMut <Args> { #[unstable(feature = "fn_traits" , issue = "29625" )] extern "rust-call" fn call (&self , args: Args) -> Self ::Output; }