Подтвердить что ты не робот

Почему аргумент для закрытия поиска требует двух амперсандов?

Я играл с Rust портировал мой движок Score4 AI, основываясь на работе над моей функциональной реализацией в OCaml. Я специально хотел посмотреть, как Rust тарифицируется с кодом функционального стиля.

Конечный результат: он работает, и он очень быстрый - намного быстрее, чем OCaml. Он почти затрагивает скорость 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() заставляют меня использовать отвратительные двойные амперсанды в параметре закрытия:

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

Почему закрытие find требует двух амперсандов? Я мог понять, почему это может понадобиться (передайте кортеж по ссылке, чтобы сэкономить время/пространство), но почему два? Это из-за iter? То есть, создается iter создание ссылок, а затем find ожидает ссылку на каждый вход, поэтому ссылка на ссылку?

Если это так, разве это, возможно, довольно уродливый недостаток дизайна в Rust?

На самом деле я ожидал бы find и map, а все остальные функциональные примитивы будут частью самих коллекций. Принуждение к iter() для выполнения каких-либо функций в функциональном стиле кажется обременительным, и даже более того, если он заставляет этот тип "двойных амперсандов" в каждой возможной функциональной цепочке.

Я надеюсь, что мне не хватает чего-то очевидного - любая помощь/разъяснение наиболее приветствуются.

4b9b3361

Ответ 1

Это здесь

moves_and_scores.iter()

дает вам итератор по заимствованным векторным элементам. Если вы следуете документу API, какой тип это, вы заметите, что это просто итератор для заимствованного фрагмента, и это реализует Iterator с Item=&T, где T есть (u32, i32) в вашем случае.

Затем вы используете find, который берет предикат, который принимает параметр &Item as. Sice Item уже является ссылкой в ​​вашем случае, предикат должен взять &&(u32, i32).

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

Вероятно, он был определен как этот, поскольку он должен был только осмотреть элемент и вернуть bool. Это не требует, чтобы элемент передавался по значению.

Если вам нужен итератор над (u32, i32), вы можете написать

moves_and_scores.iter().cloned()

cloned() преобразует итератор из одного с типом Item &T в один с типом Item T, если T - Clone. Другой способ сделать это - использовать into_iter() вместо iter().

moves_and_scores.into_iter()

Разница между двумя заключается в том, что первая опция клонирует заимствованные элементы, а вторая потребляет вектор и перемещает элементы из него.

Пишем лямбда, как это

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

вы разрушаете "двойную ссылку" и создаете локальную копию i32. Это разрешено, так как i32 - простой тип, который Copy.

Вместо деструктурирования параметра вашего предиката вы также можете написать

|move_and_score| move_and_score.1 == target_score

потому что оператор точки автоматически разыгрывает столько раз, сколько необходимо.