问题 为什么find闭包的参数需要两个&符号?


我一直在玩Rust 移植我的Score4 AI引擎 它 - 基于我在OCaml中的功能样式实现的工作。我特别想看看Rust如何使用功能风格的代码。

最终结果:它的工作原理非常快 - 比OCaml快得多。它几乎触及了命令式C / C ++的速度 - 这真的很酷。

但是有一件事让我很烦恼 - 为什么我在这段代码的最后一行需要两个&符?

let moves_and_scores: Vec<_> = moves_and_boards
    .iter()
    .map(|&(column,board)| (column, score_board(&board)))
    .collect();
let target_score = if maximize_or_minimize { 
    ORANGE_WINS 
} else { 
    YELLOW_WINS 
};
if let Some(killer_move) = moves_and_scores.iter()
    .find(|& &(_,score)| score==target_score) {
         ...

我添加它们是因为编译器错误“引导”了我;但我试图理解为什么......我使用Stack Overflow中其他地方提到的技巧来“询问”编译器告诉我什么类型的东西:

let moves_and_scores: Vec<_> = moves_and_boards
    .iter()
    .map(|&(column,board)| (column, score_board(&board)))
    .collect();
let () = moves_and_scores;

...导致此错误:

src/main.rs:108:9: 108:11 error: mismatched types:
 expected `collections::vec::Vec<(u32, i32)>`,
    found `()`
(expected struct `collections::vec::Vec`,
    found ()) [E0308]
src/main.rs:108     let () = moves_and_scores;

......正如我所料, moves_and_scores 是一个元组的向量: Vec<(u32, i32)>。但是,在下一行, iter() 和 find() 强迫我在closure参数中使用hideous double&符号:

if let Some(killer_move) = moves_and_scores.iter()
    .find(|& &(_,score)| score==target_score) {

为什么呢 find 关闭需要两个&符号?我可以看出为什么它可能需要一个(通过引用传递元组以节省时间/空间)但为什么两个?是因为 iter?就是这样的 iter 创建引用,然后 find 期望每个输入的参考,所以参考参考?

如果是这样的话,可以说,这不是Rust中一个相当难看的设计缺陷吗?

事实上,我会期待 find 和 map 以及所有其他功能原语都是集合本身的一部分。强迫我 iter() 做任何类型的功能性工作似乎很麻烦,如果它在每个可能的功能链中强制使用这种“双符号”,那就更是如此。

我希望我遗漏了一些明显的东西 - 任何帮助/澄清都是最受欢迎的。


10338
2017-11-28 11:58


起源

恭喜您管理端口,在功能样式代码上击败OCaml意味着您做了正确的事情! - Matthieu M.
@MatthieuM。谢谢!我希望有一种更清洁的方式来处理功能链(即 .iter().map(...).iter().filter() ... .iter().find(...))没有在每一步引入额外的参考水平 - 但似乎我无法避免它。 - ttsiodras


答案:


这里

moves_and_scores.iter()

给你一个迭代器  矢量元素。如果你遵循API doc这是什么类型,你会发现它只是借用切片的迭代器而且这个实现了 Iterator 同 Item=&T 哪里 T 是 (u32, i32) 在你的情况下。

然后,你用 find 需要一个带谓词的谓词 &Item 作为参数。 SICE Item 已经是你的情况下的参考,谓词必须采取 &&(u32, i32)

pub trait Iterator {
    ...
    fn find<P>(&mut self, predicate: P) -> Option<Self::Item>
    where P: FnMut(&Self::Item) -> bool {...}
    ...            ^

它可能被定义为 这个 因为它只应该检查物品并返回一个布尔。这不需要按值传递项目。

如果你想要一个迭代器 (u32, i32) 你可以写

moves_and_scores.iter().cloned()

cloned() 将迭代器从一个转换为 Item 类型 &T 一个人 Item 类型 T 如果 T 是 Clone。另一种方法是使用 into_iter() 代替 iter()

moves_and_scores.into_iter()

两者之间的区别在于第一个选项克隆借来的元素,而第二个选项使用向量并将元素移出它。

通过写这样的lambda

|&&(_, score)| score == target_score

你解构“双引用”并创建一个本地副本 i32。这是允许的 i32 是一个简单的类型 Copy

您也可以编写,而不是解构谓词的参数

|move_and_score| move_and_score.1 == target_score

因为点运算符会根据需要自动解除引用次数。


14
2017-11-28 12:26



感谢您的反馈意见!是否 iter().cloned() 有性能影响?我的意思是,它实际上是否分配“克隆”?另外,我相信 into_iter() 肯定会产生性能影响,因为它的移动语义会改变源向量。我认为最好的是你的第三个建议 - 使用“根据需要多次自动取消引用” - 必须阅读。 - ttsiodras
顺便说一句,任何关于为什么Rust选择不将功能操作符(map,filter等)作为集合的一部分的想法,并且需要 .iter() 首先(在每一步引入额外的“参考 - 间接”水平)....? - ttsiodras
@ttsiodras至于表现:没有一个真的重要。克隆一个类型的元组 (u32, i32) 很便宜。不要让“克隆”这个词吓到你。 :-)对于simpe“普通旧数据类型”,克隆和复制基本相同。但有些类型无法复制,因为它们更复杂。只有在那些情况下,我才会担心克隆。 - sellibitze
@ttsiodras你可以通过许多不同的方式迭代集合(iter, iter_mut, into_iter, drain)。每个都有它们的用途,并没有真正的“正确”方法来做到这一点。我猜,这就是为什么你必须具体。 - sellibitze