struct Data {
name: String,
id: u32,
}
impl Data {
fn getval(&self, val: Vec<u32>) -> u32 {
self.id+val[0]
}
}
fn call_closue<T>(data: &Data, f: T) -> u32
where T: Fn(&Data)->u32 {
f(data)
}
fn test(data: &Data, val: Vec<u32>) {
let _ret = call_closue(data, move |person: &Data|person.getval(val));
}
fn main() {
let a = Data{name:"a1".to_string(), id:23};
let a1 = vec![11];
test(&a, a1);
}
编译报错,没太理解为啥, 这里用 move就是想要获取vec的所有权。
error[E0507]: cannot move out of `val`, a captured variable in an `Fn` closure
--> src/main.rs:46:68
|
45 | fn test(data: &Data, val: Vec<u32>) {
| --- captured outer variable
46 | let _ret = call_closue(data, move |person: &Data|person.getval(val));
| -------------------- ^^^ move occurs because `val` has type `Vec<u32>`, which does not implement the `Copy` trait
| |
| captured by this `Fn` closure
For more information about this error, try `rustc --explain E0507`
1
共 6 条评论, 1 页
评论区
写评论要感谢rust的严格,反着分析下,假如Fn可以,那单从函数签名上就表明,下面这个函数f可以调用多次
fn call_closue<T>(data: &Data, f: T) -> u32 where T: Fn(&Data)->u32 { f(data); f(data) // 假设还能调第二次、第三次了 }
然而,传进来f真的能调用多次吗?传过来的f是下面这个move,val所有权被闭包捕获,它能调用多次吗?
fn test(data: &Data, val: Vec<u32>) { let _ret = call_closue(data, move |person: &Data|person.getval(val)); }
感觉能调用多次,然而getval的签名,是直接消费了val!这里容易被忽略
impl Data { fn getval(&self, val: Vec<u32>) -> u32 { self.id+val[0] } }
看getval的签名,如果调用一次就把val消费了,还想调用第二次、第三次,val都被销毁了,肯定不行了,所以违反Fn,只能用FnOnce
若想Fn也可以,getval的参数val要是不可变引用,闭包里面调用getval的时候也要使用person.getval(&val),即可
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=086259828b674a7a319451d64008560b
一个闭包实现了哪种 Fn 特征取决于该闭包如何使用被捕获的变量,而不是取决于闭包如何捕获它们 你的代码里
fn test(data: &Data, val: Vec<u32>) { let _ret = call_closue(data, move |person: &Data|person.getval(val)); }
上面val被闭包捕获, 调用data的getval方法, getval方法签名中val被获取了所有权 所以此闭包是一个FnOnce, 此时就算把move去掉也不影响此闭包的行为
// Fn -> FnOnce fn call_closue<T>(data: &Data, f: T) -> u32 where T: FnOnce(&Data)->u32 { f(data) } // 去掉move关键字 fn test(data: &Data, val: Vec<u32>) { let _ret = call_closue(data, |person: &Data|person.getval(val)); }
其实你的代码中根本不必获得val的所有权,
self.id+val[0]
这里你只是不可变的借用val 所以我建议你这样修改:// Vec<u32> -> &Vec<u32> fn getval(&self, val: &Vec<u32>) -> u32 { self.id+val[0] } // val -> &val fn test(data: &Data, val: Vec<u32>) { let _ret = call_closue(data, |person: &Data|person.getval(&val)); }
test 函数里面拿到了 Val 的所有权,所以要么只能调用一次(FnOnce),也可以通过传引用的方式解决这个问题:
struct Data { name: String, id: u32, } impl Data { fn getval(&self, val: &Vec<u32>) -> u32 { self.id+val[0] } } fn call_closue<T>(data: &Data, f: T) -> u32 where T: Fn(&Data)->u32 { f(data) } fn test(data: &Data, val: &Vec<u32>) { let _ret = call_closue(data, |person: &Data|person.getval(val)); } fn main() { let a = Data{name:"a1".to_string(), id:23}; let a1 = vec![11]; test(&a, &a1); }
你这个代码的完整形式是这样的,看看Fn和FnOnce的call签名,就知道为什么报错是E0507以及为什么改成FnOnce能通过了。
#![feature(unboxed_closures)] #![feature(fn_traits)] #![feature(tuple_trait)] struct Data { name: String, id: u32, } impl Data { fn getval(&self, val: Vec<u32>) -> u32 { self.id + val[0] } } struct Foo { value: Vec<u32>, } type Args<'a> = (&'a Data,); impl FnOnce<Args<'_>> for Foo { type Output = u32; extern "rust-call" fn call_once(self, args: Args) -> Self::Output { let person = args.0; person.getval(self.value) } } impl FnMut<Args<'_>> for Foo { extern "rust-call" fn call_mut(&mut self, _args: Args) -> Self::Output { unreachable!() } } impl Fn<Args<'_>> for Foo { extern "rust-call" fn call(&self, args: Args) -> Self::Output { let person = args.0; person.getval(self.value) } } fn call_closue<T>(data: &Data, f: T) -> u32 where T: Fn(&Data) -> u32, { f(data) } fn test(data: &Data, val: Vec<u32>) { // let _ret = call_closue(data, move |person: &Data|person.getval(val)); let _ret = call_closue(data, Foo { value: val }); } fn main() { let a = Data { name: "a1".to_string(), id: 23, }; let a1 = vec![11]; test(&a, a1); }
--
👇
disheng727: FnOnce是调用一次的闭包,但是代码里,并没有多次调用闭包, 改成FnOnce 为什么就可以了。。
--
👇
Bai-Jinlin: Fn -> FnOnce
FnOnce是调用一次的闭包,但是代码里,并没有多次调用闭包, 改成FnOnce 为什么就可以了。。
--
👇
Bai-Jinlin: Fn -> FnOnce
Fn -> FnOnce