< 返回版块

disheng727 发表于 2023-12-03 16:08

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`

评论区

写评论
北海 2023-12-07 12:15

要感谢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

xiami 2023-12-05 11:57

一个闭包实现了哪种 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));  
}
night-cruise 2023-12-04 09:14

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);

}
Bai-Jinlin 2023-12-03 18:27

你这个代码的完整形式是这样的,看看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

作者 disheng727 2023-12-03 17:57

FnOnce是调用一次的闭包,但是代码里,并没有多次调用闭包, 改成FnOnce 为什么就可以了。。

--
👇
Bai-Jinlin: Fn -> FnOnce

Bai-Jinlin 2023-12-03 16:48

Fn -> FnOnce

1 共 6 条评论, 1 页