Rustの独自イテレータ
イテレータのページでは、標準ライブラリが用意してくれているイテレータを使いました。vec.iter()、.map()、.filter()、.sum() などです。でも、イテレータは自分で作ることもできます。カウントダウンタイマー、フィボナッチ数列、レポートのページ、独自形式のファイルの各行など、値を1つずつ渡していくものなら、何でもイテレータにできます。
うれしいことに、書く必要があるメソッドは next の1つだけです。それだけで、map、filter、take、sum、zip など、何十ものアダプタが無料でついてきます。整理券の発券機を思い浮かべてください。あなたが作るのは「次の整理券を印刷する部分」だけです。行列も、表示板も、「ただいまの番号」の案内も、あとは全部自動で動きます。
はじめてのイテレータ:カウントダウン
Section titled “はじめてのイテレータ:カウントダウン”型をイテレータにするには、Iterator トレイトを実装します。必要なものは2つです。
type Item:イテレータが渡す値の型です(関連型については高度なトレイトのページで詳しく説明しています)。fn next(&mut self) -> Option<Self::Item>:次の値があればSome(値)を、もう値がなければNoneを返します。
struct Countdown { current: u32,}
impl Countdown { fn new(from: u32) -> Self { Countdown { current: from } }}
impl Iterator for Countdown { type Item = u32;
fn next(&mut self) -> Option<u32> { if self.current == 0 { None // 終わり } else { let value = self.current; self.current -= 1; Some(value) } }}
fn main() { let mut countdown = Countdown::new(3);
println!("{:?}", countdown.next()); println!("{:?}", countdown.next()); println!("{:?}", countdown.next()); println!("{:?}", countdown.next()); println!("{:?}", countdown.next());
for n in Countdown::new(3) { println!("{}...", n); } println!("発射!");}実行結果:
Some(3)Some(2)Some(1)NoneNone3...2...1...発射!いくつか注目してほしい点があります。
nextはイテレータを変更するので、&mut selfを受け取ります。構造体は今どこまで進んだか(current)を覚えていて、呼ばれるたびに1つ先へ進みます。だからcountdownはlet mutにする必要があります。Noneを返すのは「もう終わりです」という意味です。forループはnextを何度も呼び出し、最初にNoneが返ってきたところで止まります。- このカウントダウンは、終わったあとも
Noneを返し続けます。これは親切な作りですが、トレイトが求めているわけではありません。また値を返し始めるイテレータもありえます。その保証が必要なら、どんなイテレータにでも.fuse()を呼びましょう。
終わらないイテレータ:フィボナッチ数列
Section titled “終わらないイテレータ:フィボナッチ数列”イテレータは終わらなくてもかまいません。フィボナッチ数列(0, 1, 1, 2, 3, 5, 8, …)は永遠に続きます。どの数も、その前の2つの数を足したものです。イテレータは、直前の2つの数を覚えておくだけでいいのです。
struct Fibonacci { current: u64, next: u64,}
impl Fibonacci { fn new() -> Self { Fibonacci { current: 0, next: 1 } }}
impl Iterator for Fibonacci { type Item = u64;
fn next(&mut self) -> Option<u64> { let value = self.current; // checked_add はオーバーフローすると None を返し、`?` がその None をそのまま返す。 // そのため、数が大きくなりすぎてもクラッシュせず、イテレータが終わるだけになる。 let new_next = self.current.checked_add(self.next)?; self.current = self.next; self.next = new_next; Some(value) }}
fn main() { let first_ten: Vec<u64> = Fibonacci::new().take(10).collect(); println!("{:?}", first_ten);}実行結果:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]ポイントは .take(10) です。これがないと、collect() は数列を丸ごと集めようとしてしまいます。終わらないイテレータには、必ず take、take_while、find などで上限をつけましょう。
すべてのアダプタが無料でついてくる
Section titled “すべてのアダプタが無料でついてくる”ここがいちばんおいしいところです。Iterator トレイトにはおよそ75個のメソッドがあり、next 以外はすべて、next を使ったデフォルト実装を持っています。つまり、next を書いた瞬間に、あなたの型でそのすべてが使えるようになるのです。
struct Countdown { current: u32,}
impl Countdown { fn new(from: u32) -> Self { Countdown { current: from } }}
impl Iterator for Countdown { type Item = u32;
fn next(&mut self) -> Option<u32> { if self.current == 0 { None } else { let value = self.current; self.current -= 1; Some(value) } }}
fn main() { let total: u32 = Countdown::new(5).sum(); println!("合計: {}", total);
let evens: Vec<u32> = Countdown::new(10).filter(|n| n % 2 == 0).collect(); println!("偶数: {:?}", evens);
let labels: Vec<String> = Countdown::new(3).map(|n| format!("T-{}", n)).collect(); println!("ラベル: {:?}", labels);
let multiple = Countdown::new(100).find(|n| n % 7 == 0); println!("最初に見つかった7の倍数: {:?}", multiple);
let pairs: Vec<(u32, char)> = Countdown::new(3).zip(['a', 'b', 'c']).collect(); println!("zip した結果: {:?}", pairs);}実行結果:
合計: 15偶数: [10, 8, 6, 4, 2]ラベル: ["T-3", "T-2", "T-1"]最初に見つかった7の倍数: Some(98)zip した結果: [(3, 'a'), (2, 'b'), (1, 'c')]Rust のトレイトがなぜこんなに便利なのかがよくわかる例です。小さなメソッドを1つ書くだけで、巨大な道具箱が手に入ります。
イテレータは遅延評価される
Section titled “イテレータは遅延評価される”map や take のようなアダプタは、それだけでは何もしません。「計画」を組み立てるだけです。実際に処理が行われるのは、何かがイテレータを消費したとき、つまり for ループや collect、sum などが使われたときだけです。next が呼ばれるたびにメッセージを表示するイテレータで確かめてみましょう。
struct Noisy { n: u32,}
impl Iterator for Noisy { type Item = u32;
fn next(&mut self) -> Option<u32> { self.n += 1; println!(" next() が呼ばれて {} を渡した", self.n); Some(self.n) }}
fn main() { println!("チェーンを組み立てています..."); let chain = Noisy { n: 0 }.map(|x| x * 10).take(3); println!("まだ何も実行されていません。ここから消費します:");
for value in chain { println!("{} を受け取った", value); } println!("完了");}実行結果:
チェーンを組み立てています...まだ何も実行されていません。ここから消費します: next() が呼ばれて 1 を渡した10 を受け取った next() が呼ばれて 2 を渡した20 を受け取った next() が呼ばれて 3 を渡した30 を受け取った完了ここからわかることは2つです。1つ目に、チェーンを組み立てた時点では何も表示されていません。next() は一度も呼ばれていないのです。2つ目に、値は1つずつ流れていきます。next が1回、map が1回、「受け取った」が1回、そして次の値へ。途中に隠れたリストはありません。しかも Noisy は終わらないイテレータなのに、プログラムはちゃんと終わります。3つ受け取ったところで、take(3) がそれ以上値を求めなくなるからです。
自分のコレクションを for ループで使えるようにする
Section titled “自分のコレクションを for ループで使えるようにする”ここまでの構造体は、それ自体がイテレータでした。でも実際には、要素を持っているコレクションの構造体があって、Vec と同じようにループで回せるようにしたい、ということもよくあります。そのときは IntoIterator を実装します。これは for ループが裏で使っているトレイトです。for x in thing と書くと、実際には thing.into_iter() が呼ばれています。
コレクションには、ふつうループの回し方が3通りあります。自分の型で並べてみるとわかりやすいでしょう。
| 書き方 | 裏で使われるメソッド | 各要素の型 | ループのあとのコレクション |
|---|---|---|---|
for s in &playlist |
playlist.iter() |
&String(読み取り専用) |
まだ使える |
for s in &mut playlist |
playlist.iter_mut() |
&mut String(変更できる) |
まだ使える |
for s in playlist |
playlist.into_iter() |
String(所有権あり) |
ムーブされて使えない |
次の Playlist は、この3つすべてに対応しています。曲は Vec に入っているので、新しいイテレータを書かなくても、Vec のイテレータをそのまま渡せます。
struct Playlist { songs: Vec<String>,}
impl Playlist { fn iter(&self) -> std::slice::Iter<'_, String> { self.songs.iter() }
fn iter_mut(&mut self) -> std::slice::IterMut<'_, String> { self.songs.iter_mut() }}
// `for song in &playlist` を使えるようにするimpl<'a> IntoIterator for &'a Playlist { type Item = &'a String; type IntoIter = std::slice::Iter<'a, String>;
fn into_iter(self) -> Self::IntoIter { self.iter() }}
// `for song in &mut playlist` を使えるようにするimpl<'a> IntoIterator for &'a mut Playlist { type Item = &'a mut String; type IntoIter = std::slice::IterMut<'a, String>;
fn into_iter(self) -> Self::IntoIter { self.iter_mut() }}
// `for song in playlist` を使えるようにする(プレイリストは使い切られる)impl IntoIterator for Playlist { type Item = String; type IntoIter = std::vec::IntoIter<String>;
fn into_iter(self) -> Self::IntoIter { self.songs.into_iter() }}
fn main() { let mut playlist = Playlist { songs: vec![String::from("intro"), String::from("main theme"), String::from("outro")], };
for song in &playlist { println!("読む: {}", song); }
for song in &mut playlist { *song = song.to_uppercase(); }
println!("いちばん長い曲名: {:?}", playlist.iter().max_by_key(|s| s.len()));
for song in playlist { println!("取り出す: {}", song); } // 最後のループで `playlist` はムーブされたので、ここではもう使えない。}実行結果:
読む: intro読む: main theme読む: outroいちばん長い曲名: Some("MAIN THEME")取り出す: INTRO取り出す: MAIN THEME取り出す: OUTRO曲名を英語のままにしているのは、to_uppercase() で大文字にする様子を見せるためと、s.len() が文字数ではなくバイト数を数えるからです(日本語は1文字が3バイトになります)。
行数は多いですが、どの impl も同じ形をしています。Item が何かを書き、返すイテレータの型(IntoIter)を書き、それを返すだけです。'a はライフタイムで、「借りた要素は、借りたプレイリストと同じだけ生きる」という意味です。Vec を包んでいるコレクションなら、ほとんどの場合このパターンをそのまま使い回せます。
逆向きに進む、長さを知る
Section titled “逆向きに進む、長さを知る”知っておくと便利なトレイトがあと2つあります。それほど頻繁には使いませんが、.rev() や .len() のようなアダプタはこれらに頼っています。
DoubleEndedIteratorは、末尾から要素を取り出すnext_backを追加します。これで.rev()が使えるようになります。ExactSizeIteratorは、残りの要素数をイテレータが正確に知っていることを約束します。これで.len()が使えるようになります。あわせて、Iteratorの中で正確なsize_hintを返すようにします。
// low, low+1, ..., high-1 を順に返す(範囲 low..high と同じ)struct Between { low: u32, high: u32,}
impl Iterator for Between { type Item = u32;
fn next(&mut self) -> Option<u32> { if self.low >= self.high { return None; } let value = self.low; self.low += 1; Some(value) }
fn size_hint(&self) -> (usize, Option<usize>) { let left = self.high.saturating_sub(self.low) as usize; (left, Some(left)) }}
impl DoubleEndedIterator for Between { fn next_back(&mut self) -> Option<u32> { if self.low >= self.high { return None; } self.high -= 1; Some(self.high) }}
impl ExactSizeIterator for Between {}
fn main() { let backwards: Vec<u32> = Between { low: 1, high: 6 }.rev().collect(); println!("{:?}", backwards);
let mut both_ends = Between { low: 1, high: 6 }; println!("先頭 {:?}、末尾 {:?}", both_ends.next(), both_ends.next_back()); println!("残り {} 個", both_ends.len());}実行結果:
[5, 4, 3, 2, 1]先頭 Some(1)、末尾 Some(5)残り 3 個両端がお互いに近づいていき、ぶつかったところでイテレータは終わります。impl ExactSizeIterator for Between {} の中身は空です。len() は、すでに書いた size_hint を使ってくれるからです。
近道:from_fn と successors
Section titled “近道:from_fn と successors”構造体と impl を書く方法は、何度も使うイテレータにぴったりです。その場限りのちょっとしたイテレータなら、標準ライブラリの std::iter に2つの便利な関数があります。
iter::from_fn(クロージャ):値が必要になるたびに、あなたのクロージャを呼び出します。クロージャはnextと同じようにSome(値)かNoneを返します。iter::successors(最初の値, クロージャ):最初の値から始めて、クロージャが1つ前の値から次の値を計算します。Noneを返すと終わります。
use std::iter;
fn main() { // from_fn:クロージャが自分の状態(count)を持っている let mut count = 3; let countdown: Vec<u32> = iter::from_fn(|| { if count == 0 { None } else { count -= 1; Some(count + 1) } }) .collect(); println!("{:?}", countdown);
// successors:それぞれの値を1つ前の値から計算する let powers: Vec<u32> = iter::successors(Some(1u32), |&n| n.checked_mul(10)).collect(); println!("{:?}", powers);
// 1行でフィボナッチ数列:直前の2つの数をペアにして持ち回す let fib: Vec<u64> = iter::successors(Some((0u64, 1u64)), |&(a, b)| Some((b, a + b))) .map(|(a, _)| a) .take(10) .collect(); println!("{:?}", fib);}実行結果:
[3, 2, 1][1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000][0, 1, 1, 2, 3, 5, 8, 13, 21, 34]powers は自分で止まります。次の10のべき乗である 10,000,000,000 は u32 に収まらないので、checked_mul が None を返し、イテレータが終わるのです。目安としては、1つの関数の中だけで使うならこうした近道を使い、ほかのコードからもそのイテレータを作りたいときや、状態がたくさんあるときは、名前のある構造体を書きましょう。
よくある間違い
Section titled “よくある間違い”自分で next を呼ぶときに mut を忘れる。 next はイテレータを変更するので、変数は可変でなければなりません。
let countdown = Countdown { current: 3 };println!("{:?}", countdown.next());error[E0596]: cannot borrow `countdown` as mutable, as it is not declared as mutable --> src/main.rs:20:22 |20 | println!("{:?}", countdown.next()); | ^^^^^^^^^ cannot borrow as mutable |help: consider changing this to be mutable |19 | let mut countdown = Countdown { current: 3 }; | +++エラーは「countdown は可変として宣言されていないので、可変で借用できません」と言っています。mut を付けましょう(for ループでは必要ありません。ループがイテレータを受け取って、そのあたりを引き受けてくれるからです)。
type Item を書き忘れる。 トレイトは、あなたが何を渡すのかを知る必要があります。
error[E0046]: not all trait items implemented, missing: `Item` --> src/main.rs:5:1 |5 | impl Iterator for Countdown { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `Item` in implementation | = help: implement the missing item: `type Item = /* Type */;`このメッセージは「トレイトの項目がすべて実装されていません。Item がありません」という意味です。impl の中に type Item = u32;(またはイテレータが返す型)を追加しましょう。
終わらないイテレータを collect する。 Fibonacci::new().collect::<Vec<_>>()(take なし)は問題なくコンパイルできますが、役に立つ形では終わりません。数がオーバーフローするまで動き続けるか、next が一度も None を返さなければ永遠に動き続けます。これはコンパイラには見つけられません。終わらない可能性のあるイテレータは、消費する前に必ず take、take_while、find を付けましょう。
Iteratorは、type Itemとfn next(&mut self) -> Option<Self::Item>で実装します。終わったらNoneを返します。nextさえあれば、map、filter、take、sum、zipなど何十ものメソッドが無料で使えます。- イテレータは遅延評価されます。
forループやcollect、sumなどが消費するまで何も起こらず、要素は1つずつ流れていきます。 &MyType、&mut MyType、MyTypeにIntoIteratorを実装すると、借用・可変借用・消費の3通りのforループに対応できます。DoubleEndedIteratorで.rev()が、ExactSizeIteratorで.len()が使えるようになります。- その場限りのイテレータには、
std::iter::from_fnやstd::iter::successorsを使いましょう。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. もう要素がないとき、next は何を返すべきですか?
2. next だけを実装したイテレータで、.map() や .sum() を呼べますか?
3. 遅延評価の例で、next() が最初に呼ばれるのはいつですか?
4. for song in &playlist を使えるようにするのはどの impl ですか?
5. 自分のイテレータで .rev() を使えるようにするには、どのトレイトが必要ですか?
スコア: 0 / 5
さらに詳しく
Section titled “さらに詳しく”- 一連の要素をイテレータで処理する — The Rust Programming Language 日本語版:
Iteratorとnextの仕組みと、イテレータが遅延評価される理由。 std::iterモジュール — 標準ライブラリのドキュメント(英語):イテレータの公式ガイドです。独自のイテレータの実装方法やIntoIteratorについての節もあります。Iteratorトレイト — 標準ライブラリのドキュメント(英語):nextを書くだけで無料でついてくるアダプタの一覧です。- イテレータ — Rust By Example 日本語版:実行して書き換えられる、フィボナッチ数列のイテレータの短い例です。
std::iter::successors(英語):最初の値と「次の値」のルールからイテレータを作る関数のドキュメントです。