一个闭包去糖化成一个实现Fn特征的匿名结构(std::ops::{Fn, FnOnce, FnMut}尽可能,查看更多in the docs。你的第一个闭包只实现FnOnce。你的第二个实现FnMut,也是)。
要了解这里发生了什么,让我们自己对闭包进行脱糖(注意:正确的脱糖需要每晚进行一次,因为实现 Fn* 特征是不稳定的):
struct Closure1 {
ssa: String,
}
impl FnOnce<()> for Closure1 {
type Output = ();
extern "rust-call" fn call_once(mut self, (): ()) {
self.ssa = Add::add(self.ssa, "ds");
println!("{}", self.ssa);
}
}
let /*mut*/ ssa = String::new();
let rr = Closure1 { ssa };
FnOnce::call_once(rr, ());
struct Closure2<'a> {
ssa: &'a mut String,
}
impl FnOnce<()> for Closure2<'_> {
type Output = ();
extern "rust-call" fn call_once(mut self, (): ()) {
FnMut::call_mut(&mut self, ())
}
}
impl FnMut<()> for Closure2<'_> {
extern "rust-call" fn call_mut(&mut self, (): ()) {
AddAssign::add_assign(&mut *self.ssa, "ds");
println!("{}", self.ssa);
}
}
let mut ssa = String::new();
let mut rr = Closure2 { ssa: &mut ssa };
FnMut::call_mut(&mut rr, ());
Playround.
我们这里有什么?
第一个闭包分配给ssa;这意味着它需要控制它。因此,它被移入了闭包本身。这也是它只实现FnOnce的原因。
另一方面,第二个闭包仅变异 ssa:它只需要一个&mut 对其的引用。
调用闭包时,第一个闭包实现FnOnce:调用时以FnOnce::call_once()调用;移动关闭。如果您尝试再次调用它,它将因“使用移动值”而失败。
但是第二个闭包是用FnMut::call_mut() 调用的(因为编译器总是用最不“强”的特征调用),它需要&mut self。这意味着它不会移动闭包——因此你可以调用它两次,但另一方面,你需要对闭包进行可变引用——所以它需要被标记为mut!
(如果您想知道为什么第一个闭包需要在移动它时将ssa 标记为mut,确实,请注意在脱糖中我注释掉了mut。但是,编译器知道闭包并认为闭包中的赋值需要mut 访问,因为如果不这样做会很混乱。)