IntoIter
Rust では、コレクションは Iterator トレイトを使ってイテレートされます。これは Drop よりも少し複雑です。
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
ここで新しく登場したのは type Item です。これは、Iterator のすべての実装が Item という関連型を持つことを宣言しています。この場合、これは next を呼び出したときに吐き出せる型です。
Iterator が Option<Self::Item> を返す理由は、このインターフェイスが has_next と get_next の概念をまとめているからです。次の値がある場合は
Some(value) を返し、ない場合は None を返します。これにより、API は一般に使いやすく安全に使用・実装できるようになり、同時に has_next と get_next の間の冗長なチェックやロジックを避けられます。いいですね!
残念ながら、Rust には yield 文のようなものは(まだ)ないので、そのロジックは自分で実装する必要があります。また、実際には各コレクションが実装するよう努めるべきイテレータが 3 種類あります。
- IntoIter -
T - IterMut -
&mut T - Iter -
&T
実のところ、List のインターフェイスを使って IntoIter を実装するための道具はすでにすべて揃っています。単に pop を何度も呼び出せばよいのです。そのため、IntoIter は List を包む newtype ラッパーとして実装するだけにします。
// タプル構造体は構造体の別形式で、
// 他の型の簡単なラッパーに便利です。
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
いいですね!