IntoIter
Перебор коллекций в Rust выполняется с помощью типажа Iterator.
Он ненамного сложнее, чем Drop:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
Новая штука здесь — это type Item.
Она объявляет, что каждая реализация типажа Iterator имеет связанный с ней тип, называемый Iter.
В это тот самый тип, который возвращает функция next.
Причина по которой интерфейс Iterator возвращает значения Option<Self::Item> в том, что он объединяет концепции has_next (есть ли следующий элемент?) и get_next (верни следующий элемент).
Когда у вас есть следующее значение, вы возвращаете Some(value), а когда нет — None.
Такой подход делает API более эргономичным и безопасным как для реализации, так и для использования, поскольку позволяет убрать проверки и логику между has_next и get_next.
Прекрасно!
К сожалению, (пока) в Rust нет ничего похожего на оператор yield, так что нам предстоит самостоятельно реализовывать логику перебора.
Есть три различных вида итераторов, которые должна постараться реализовать каждая коллекция:
- IntoIter (итератор по значениям) —
T - IterMut (итератор по изменяемым ссылкам) —
&mut T - Iter (итератор по разделяемым ссылкам) —
&T
На самом деле у нас уже есть всё, что нужно, чтобы реализовать IntoIter используя интерфейс List: нам всего лишь надо раз за разом вызывать pop.
Поэтому мы просто реализуем IntoInter, как новый тип поверх List:
// Кортежи — альтернативная форма структур,
// полезная для тривиальных обёрток вокруг других типов.
pub struct IntoIter<T>(List<T>);
impl<T> List<T> {
pub fn into_iter(self) -> IntoIter<T> {
IntoIter(self)
}
}
impl<T> Iterator for IntoIter<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
// получаем доступ к полям кортежа по номеру
self.0.pop()
}
}
Ну, а теперь напишем тест:
#[test]
fn into_iter() {
let mut list = List::new();
list.push(1); list.push(2); list.push(3);
let mut iter = list.into_iter();
assert_eq!(iter.next(), Some(3));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), None);
}
> cargo test
Running target/debug/lists-5c71138492ad4b4a
running 4 tests
test first::test::basics ... ok
test second::test::basics ... ok
test second::test::into_iter ... ok
test second::test::peek ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured
Прекрасно!