我在将引用计数的变量移动到需要实现FnMut
的闭包中时遇到了一些问题。
以下代码运行良好:
use std::rc::Rc;
fn main() {
let callback;
let data: Rc<Vec<f32>> = Rc::new(Vec::new());
{
let data = data.clone();
callback = move || {
data.iter().for_each(|v| println!("{}", v));
};
consumer(callback);
}
}
fn consumer<D>(callback: D)
where
D: FnMut(),
{
}
如果我将闭包更改为:
callback = move || {
data;
};
编译器产生错误:
--> src/test1.rs:11:20
|
11 | callback = move || {
| ^^^^^^^ this closure implements `FnOnce`, not `FnMut`
12 | data;
| ---- closure is `FnOnce` because it moves the variable `data` out of its environment
...
15 | consumer(callback);
| -------- the requirement to implement `FnMut` derives from here
此示例有效:
callback = move || {
let x = data[0];
println!("{}", x);
};
这个例子没有:
callback = move || {
let x = data;
println!("{}", x[0]);
};
对于let data: Arc<Mutex<Vec<f32>>> = Arc::new(Mutex::new(Vec::new()));
,这是有效的:
callback = move || {
let x = data.lock().unwrap();
};
这不是:
callback = move || {
let x = data;
};
这背后的原因是什么?
以下代码运行良好:
let data: Rc<Vec<f32>> = Rc::new(Vec::new()); { let data = data.clone(); callback = move || { data.iter().for_each(|v| println!("{}", v)); };
首先,当构造闭包时,data
被移动到其中,因为闭包被声明为move ||
。这就是为什么如果你想在闭包之外再次使用data
,你需要提前克隆
然后,当调用闭包时,iter()
是一个采用&self
的方法,因此data
不会被方法调用移出;它是借来的,闭包仍然拥有它。因此,闭包可以被调用任意多次——它自动实现Fn
,而不仅仅是FnOnce
。
callback = move || { data; };
callback = move || { let x = data; println!("{}", x[0]); };
这里,当调用闭包时,data
会被移动(按值使用,而不是按引用使用(。在第一种情况下,它立即被丢弃;在第二个步骤中,它被移动到局部变量CCD_。
如果一个值从闭包中移出,那么这个闭包只实现FnOnce
,并且只能调用一次,因为如果第二次调用这个闭包,它就不会再使用这个值了。
一般来说,任何简单提及变量名的行为都是一种行为其他一切都是一个特殊的例外:
- 如果该值实现
Copy
特征,则它可以是副本而不是移动,这意味着原始值保持有效。(此处不适用,因为Rc
不实现Copy
。( - 如果调用
.iter()
或.lock()
等方法,则该方法可以采用&self
或&mut self
;Rust会自动获取引用,就好像您编写了(&data).iter()
一样 - 为了方便起见,
println!
是一个隐式引用其参数的宏
如果改为编写&data;
或let x = &data;
,则将创建一个引用,该引用不会移动原始值。
为了从与公认答案略有不同的角度讨论同一主题,有两个概念是相关但不同的:
-
move
确保data
值移动到闭包中,并且不会超过闭包。 -
以上与是否仅调用闭包需要消耗CCD_ 27无关。
在move || data.lock().unwrap()
的情况下,闭包的调用实现采用&self
,并且可以被多次调用,只有当闭包被丢弃时才会破坏data
。
在move || data
的情况下,闭包的调用实现采用self
并立即消耗数据(以及闭包的其余部分(,这就是为什么它只能被调用一次,并且在返回时将丢弃data
。
在#2的情况下,闭包的主体消耗数据(move || data
或仅|| data
(意味着闭包拥有捕获的值的所有权。相反的情况并不适用:使用move
强制闭包接管值的所有权这一事实并不意味着调用将消耗该值。但是,如果您愿意,您可以通过在闭包体中执行类似drop(data)
的操作来强制执行,这将使您的闭包成为FnOnce
。