Почему аргумент для закрытия поиска требует двух амперсандов?
Я играл с 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()
для выполнения каких-либо функций в функциональном стиле кажется обременительным, и даже более того, если он заставляет этот тип "двойных амперсандов" в каждой возможной функциональной цепочке.
Я надеюсь, что мне не хватает чего-то очевидного - любая помощь/разъяснение наиболее приветствуются.
Ответы
Ответ 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
потому что оператор точки автоматически разыгрывает столько раз, сколько необходимо.