コンテンツにスキップ

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)
None
None
3...
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つ書くだけで、巨大な道具箱が手に入ります。

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 を包んでいるコレクションなら、ほとんどの場合このパターンをそのまま使い回せます。

知っておくと便利なトレイトがあと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 を使ってくれるからです。

構造体と 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つの関数の中だけで使うならこうした近道を使い、ほかのコードからもそのイテレータを作りたいときや、状態がたくさんあるときは、名前のある構造体を書きましょう。

自分で 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 を使いましょう。

理解度チェック

1. もう要素がないとき、next は何を返すべきですか?

2. next だけを実装したイテレータで、.map() や .sum() を呼べますか?

3. 遅延評価の例で、next() が最初に呼ばれるのはいつですか?

4. for song in &playlist を使えるようにするのはどの impl ですか?

5. 自分のイテレータで .rev() を使えるようにするには、どのトレイトが必要ですか?

スコア: 0 / 5