这篇文章首要记录了我在看了一篇关于Rust闭包的文章后引出的一系列考虑与总结。
Understanding Closures in Rust.
在文章中,作者的结论如下:
- 不捕获任何环境中变量的闭包能够转换为函数指针。这一点在The Rust Reference和Rust RFC中都有所提及。
- 闭包的办法经过不可变引证访问其捕获的变量的闭包完成了Fn
- 闭包的办法经过可变引证访问其捕获变量的闭包完成了FnMut
- 闭包的办法若获取了只能被调用一次,即完成了FnOnce
而且作者画出了一张图来表示FnOnce、FnMut、Fn与所捕获的变量之间的联系:
在这篇文章中,并未提及我感兴趣的关键字move对闭包的影响。此外我对作者开篇画出的图也有疑问。因而我觉得有必要深化考虑有封闭包的一些问题。
闭包
闭包在Rust中的完成能够近似地理解为一个完成了FnOnce、FnMut和Fn其中一个trait的匿名结构体,这个匿名结构体保存捕获的环境中的变量。经过调用trait的办法来执行闭包体中的代码。
FnOnce、FnMut与Fn
先来看看标准库中三者的界说:
// FnOnce
#[lang = "fn_once"]
#[must_use = "closures are lazy and do nothing unless called"]
pub trait FnOnce<Args> {
type Output;
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
// FnMut
#[lang = "fn_mut"]
#[must_use = "closures are lazy and do nothing unless called"]
pub trait FnMut<Args>: FnOnce<Args> {
extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}
// Fn
#[lang = "fn"]
#[must_use = "closures are lazy and do nothing unless called"]
pub trait Fn<Args>: FnMut<Args> {
extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}
从这三个trait的声明能够看出,Fn是FnMut的子trait,FnMut是FnOnce的子trait。也就是说完成了Fn的闭包一定完成了FnMut,相同,完成了FnMut的闭包一定完成了FnOnce。
闭包完成这三个trait的规矩如下:
- 所有的闭包都完成了FnOnce。
- 假如闭包的办法移出了所捕获的变量的所有权,则只会完成FnOnce。
- 假如闭包的办法没有移出所捕获的变量的所有权,而且对变量进行了修正,即经过可变借用运用所捕获的变量,则会完成FnMut。
- 假如闭包的办法没有移出所捕获的变量的所有权,而且没有对变量进行修正,即经过不可变借用运用所捕获的变量,则会完成Fn。
图示:
它们的联系如图:
关键字move
关键字move的作用是将所引证的变量的所有权转移至闭包内,通常用于使闭包的生命周期大于所捕获的变量的原生命周期(例如将闭包回来或移至其他线程)。
捕获形式
闭包捕获环境中变量的形式为优先不可变借用,而后依次为仅有不可变借用(例如&&mut T),可变借用,移动。
- 当闭包借用环境中的变量时,引证变量&T(或&mut T)将保存在闭包匿名结构体中。此刻若想获取所引证的变量的所有权,就要运用move关键字将其所有权转移至闭包中,闭包会捕获所引证的变量自身T(或mut T),也就是下面所要说的情况。
- 当闭包移动环境中的变量时,闭包会依据其语义进行Move或Copy。捕获的变量T(或mut T)将保存在闭包匿名结构体中。
那么,简单地来说,假如闭包捕获的变量为引证&T(或&mut T),运用关键字move后,闭包会依据所引证的目标的语义(Copy或Move)捕获T(或mut T)。
注意,在标准库文档和The Rust Reference中都明确说明了闭包完成FnOnce、FnMut和Fn中的哪个trait只与闭包怎么运用所捕获的变量有关,与怎么捕获变量无关。关键字move影响的是闭包怎么捕获变量,因而,对闭包完成FnOnce、FnMut和Fn没有任何影响。
补充:怎么捕获变量指的是闭包将捕获的变量以何种形式存储在匿名结构体中(值或引证),与闭包怎么运用捕获的变量无关。
Copy trait
由于在运用关键字move后,闭包捕获的变量的所有权会发生变化,因而会对闭包发生别的一个影响,即闭包自身是否会完成Copy trait。
关于这一点其实依据捕获变量的语义很好判断。
- 假如闭包捕获的变量为Copy语义,闭包会完成Copy trait
- 假如闭包捕获的变量为Move语义,则闭包不会完成Copy trait
此外咱们知道&T为Copy语义,&mut T为Move语义,再依据上面两条规矩,就能够得出:
- 闭包捕获的变量为不可变引证&T或Copy语义的T时,闭包会完成Copy trait
- 闭包捕获的变量为可变引证&mut T或Move语义的T时,则闭包不会完成Copy trait
闭包是否完成Copy trait,只与捕获的变量是否能够被copy有关,与怎么运用(是否修正捕获的变量)无关。
一个比如
#[derive(Copy, Clone)]
struct FooCopy {
value: i32,
}
impl FooCopy {
fn new(value: i32) -> Self {
Self { value }
}
fn get(&self) -> i32 {
self.value
}
fn increase(&mut self) {
self.value += 1;
}
}
fn is_FnMut<F: FnMut()>(_closure: &F) {}
fn is_Copy<F: Copy>(_closure: &F) {}
fn main() {
let mut foo_copy = FooCopy::new(0);
let mut c_with_move = move || {
for _ in 0..5 {
foo_copy.increase();
}
println!("foo_copy in closure(with move): {}", foo_copy.get());
};
c_with_move();
println!("foo_copy out of closure: {}\n", foo_copy.get());
let mut c_without_move = || {
for _ in 0..5 {
foo_copy.increase();
}
println!("foo_copy in closure(without move): {}", foo_copy.get());
};
is_FnMut(&c_with_move);
is_Copy(&c_with_move);
is_FnMut(&c_without_move);
//is_Copy(&c_without_move); // Error
c_without_move();
println!("foo_copy out of closure(without move): {}\n", foo_copy.get());
c_with_move();
println!("foo_copy out of closure(with move): {}\n", foo_copy.get());
}
输出:
foo_copy in closure(with move): 5
foo_copy out of closure: 0
foo_copy in closure(without move): 5
foo_copy out of closure(without move): 5
foo_copy in closure(with move): 10
foo_copy out of closure(with move): 5
比如中Copy语义的变量foo_copy在运用关键字move将其Copy至闭包c_with_move内后,对环境中的变量不再有影响。此刻闭包的匿名结构体中保存的变量为mut FooCopy,在闭包中运用的increase()办法经过可变借用来进行操作,所以完成了FnMut + Copy trait。
在不运用关键字move时,闭包c_without_move对环境中的变量foo_copy进行了可变借用。此刻闭包的匿名结构体内中保存的变量为&mut FooCopy,所以会对环境中的变量进行修正,其相同完成了FnMut trait,但不会完成Copy trait。
总结
- 闭包完成FnOnce、FnMut和Fn中的哪个trait只与闭包怎么运用所捕获的变量有关,与怎么捕获变量无关。因而,关键字move不影响闭包完成FnOnce、FnMut和Fn。
- 在实际运用中,个人认为其实只需要考虑闭包办法是怎么运用捕获的变量即可,简直不需要考虑闭包自身是否完成Copy trait。关键字move首要用于使闭包脱节所捕获的变量的生命周期限制,例如将闭包回来或移至其他线程时,有必要运用move。
Quiz
最终,经过一个Quiz来加深理解,牢记不要经过此图表来回忆,理解了相关概念后,也无需回忆。
参考
- Andrew Pritchard (25. Jun, 2019)Understanding Closures in Rust.
- The Rust RFC Book
- The Rust Standard Library
- The Rust Reference
引自: zhuanlan.zhihu.com/p/341815515