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

2023-08-30 18:02:31 +08:00
 Number13

下面这段代码中,闭包体为什么变成了 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);
}
1418 次点击
所在节点    程序员
10 条回复
hsfzxjy
2023-08-30 18:10:49 +08:00
这段代码里闭包也是 FnMut 呀
BBCCBB
2023-08-30 18:27:22 +08:00
应该就是所有权吧. value move 到了这个闭包里.
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
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
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
2023-08-30 20:32:39 +08:00
Fn 、FnMut 、FnOnce 的区别建议看下 https://rustcc.cn/article?id=8b6c5e63-c1e0-4110-8ae8-a3ce1d3e03b9
FreeWong
2023-08-31 10:58:16 +08:00
sort_operations.push(value); 你自己要求要 push 一个 String 类型的 value, 然而 sort_by_key 对 闭包的要求是 FnMut ,即可以对周围环境变量的捕获是 Fn,FnMut ,所以就冲突了
Number13
2023-08-31 14:04:29 +08:00
@lsk569937453 666 ,我就是陷入了误解四,认为 String 是否 clone 会影响到闭包体是否为 FnOnce 。
@ie88 哦哦,是这样啊,这块我想到了,但是我以为这个所有权是给了 vec 看你解释,明白了一些了,是闭包体把 vec 和这个 String 的所有权都捕获了吧。
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
2023-08-31 14:33:09 +08:00
我把你的代码改写成了底层形式,你看看是不是一样的错误
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=9c32461c6ece185b284827fd296cc1c9

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/969557

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX