V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Number13
V2EX  ›  程序员

来个大佬教教我,这个闭包体为什么是 FnOnce

  •  
  •   Number13 · 2023-08-30 18:02:31 +08:00 · 1389 次点击
    这是一个创建于 460 天前的主题,其中的信息可能已经有所发展或是发生改变。

    下面这段代码中,闭包体为什么变成了 FnOnce ?是因为 Value 被 push 到 vec 中,导致所有权转移,所以 rust 认为它是 FnOnce 的闭包体了么?还是因为什么?求解答

    #[derive(Debug)]
    struct Rectangle {
        width: u32,
        height: u32,
    }
    
    fn main() {
        let mut list = [
            Rectangle { width: 10, height: 1 },
            Rectangle { width: 3, height: 5 },
            Rectangle { width: 7, height: 12 },
        ];
    
        let mut sort_operations = vec![];
        let  value = String::from("by key called");
    
        list.sort_by_key(|r| {
            sort_operations.push(value);    // 如果这里改成 sort_operations.push(value.clone()); 
            				// 那么这个闭包体就是 FnMut 类型了
            r.width
        });
        println!("{:#?}", list);
    }
    
    10 条回复    2023-08-31 14:33:09 +08:00
    hsfzxjy
        1
    hsfzxjy  
       2023-08-30 18:10:49 +08:00 via Android
    这段代码里闭包也是 FnMut 呀
    BBCCBB
        2
    BBCCBB  
       2023-08-30 18:27:22 +08:00
    应该就是所有权吧. value move 到了这个闭包里.
    xring
        3
    xring  
       2023-08-30 19:55:16 +08:00
    Fn: the closure uses the captured value by reference (&T)
    FnMut: the closure uses the captured value by mutable reference (&mut T)
    FnOnce: the closure uses the captured value by value (T)
    ie88
        4
    ie88  
       2023-08-30 20:10:47 +08:00
    你这段代码会报错,你从哪里看到 FnMut 变成了 FnOnce ?
    想要实现查看 sort_by_key 执行多少次,把 String::from() 换成 &str ,
    即 let value = "by key called";
    编译器已经提示你 "move occurs because `value` has type `String`, which does not implement the `Copy` trait"
    ie88
        5
    ie88  
       2023-08-30 20:19:26 +08:00
    @ie88 再详细点说,就是 这个 value (String 类型) 第一次被 push 到 sort_operations 这个 Vec 里,就已经被 move 到了 closure ,在这个作用域内已经没有了这个 value ,所以下一次 进行 push 操作时,找不到这个 value 了,因为 你定义的 value 是 String 类型,不具有 Copy trait
    如果你 定义 value 时,像我上面写的,用 &str ,move 到 closure 时就会隐式发生 copy
    lsk569937453
        6
    lsk569937453  
       2023-08-30 20:32:39 +08:00   ❤️ 1
    Fn 、FnMut 、FnOnce 的区别建议看下 https://rustcc.cn/article?id=8b6c5e63-c1e0-4110-8ae8-a3ce1d3e03b9
    FreeWong
        7
    FreeWong  
       2023-08-31 10:58:16 +08:00
    sort_operations.push(value); 你自己要求要 push 一个 String 类型的 value, 然而 sort_by_key 对 闭包的要求是 FnMut ,即可以对周围环境变量的捕获是 Fn,FnMut ,所以就冲突了
    Number13
        8
    Number13  
    OP
       2023-08-31 14:04:29 +08:00
    @lsk569937453 666 ,我就是陷入了误解四,认为 String 是否 clone 会影响到闭包体是否为 FnOnce 。
    @ie88 哦哦,是这样啊,这块我想到了,但是我以为这个所有权是给了 vec 看你解释,明白了一些了,是闭包体把 vec 和这个 String 的所有权都捕获了吧。
    PTLin
        9
    PTLin  
       2023-08-31 14:13:23 +08:00
    闭包可以看作一个结构体,你对闭包的使用方式决定了闭包的捕获方式,捕获方式决定了闭包实现了什么 trait 。

    假设有个结构体,你这段代码将 value push 到了 vec 中,故捕获了&mut vec 。value 是所有权方式使用的,故捕获了 string 。
    这时这个闭包的结构体中就有两个字段,分别是&mut vec ,value 。

    调用 FnOnce 闭包在底层相当于调用了闭包结构体的 fn call_once(self, args: Args) -> Self::Output 方法。
    调用 FnMut 闭包在底层相当于调用了闭包结构体的 fn call_mut(&mut self, args: Args) -> Self::Output 方法。

    由此可知假设你传入的闭包实现了 FnMut ,此时将会调用 fn call_mut(&mut self, args: Args) -> Self::Output 。这时就会出现问题,代码中将 string 类型的 value ,push 到了&mut vec 中,但由于 self 是&mut ,无法对 value 转移出所有权,所以 error 的提示为[E0507]: cannot move out of `value`。
    PTLin
        10
    PTLin  
       2023-08-31 14:33:09 +08:00
    我把你的代码改写成了底层形式,你看看是不是一样的错误
    https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=9c32461c6ece185b284827fd296cc1c9
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4578 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 04:05 · PVG 12:05 · LAX 20:05 · JFK 23:05
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.