一連の要素をイテレータで処理する

イテレータパターンにより、一連の要素に順番に何らかの作業を行うことができます。イテレータは、 各要素を繰り返し、シーケンスが終わったことを決定するロジックの責任を負います。イテレータを使用すると、 自身でそのロジックを再実装する必要がなくなるのです。

Rustにおいて、イテレータは怠惰です。つまり、イテレータを使い込んで消費するメソッドを呼ぶまで何の効果もないということです。 例えば、リスト13-13のコードは、Vec<T>に定義されたiterメソッドを呼ぶことでv1ベクタの要素に対するイテレータを生成しています。 このコード単独では、何も有用なことはしません。


#![allow(unused)]
fn main() {
let v1 = vec![1, 2, 3];

let v1_iter = v1.iter();
}

リスト13-13: イテレータを生成する

一旦イテレータを生成したら、いろんな手段で使用することができます。第3章のリスト3-5では、 ここまでiterの呼び出しが何をするかごまかしてきましたが、forループでイテレータを使い、 各要素に何かコードを実行しています。

リスト13-14の例は、イテレータの生成とforループでイテレータを使用することを区別しています。 イテレータは、v1_iter変数に保存され、その時には繰り返しは起きていません。v1_iterのイテレータで、 forループが呼び出された時に、イテレータの各要素がループの繰り返しで使用され、各値が出力されます。


#![allow(unused)]
fn main() {
let v1 = vec![1, 2, 3];

let v1_iter = v1.iter();

for val in v1_iter {
    // {}でした
    println!("Got: {}", val);
}
}

リスト13-14: forループでイテレータを使用する

標準ライブラリにより提供されるイテレータが存在しない言語では、変数を添え字0から始め、 その変数でベクタに添え字アクセスして値を得て、ベクタの総要素数に到達するまでループでその変数の値をインクリメントすることで、 この同じ機能を書く可能性が高いでしょう。

イテレータはそのロジック全てを処理してくれるので、めちゃくちゃにしてしまう可能性のあるコードの繰り返しを減らしてくれます。 イテレータにより、添え字を使えるデータ構造、ベクタなどだけではなく、多くの異なるシーケンスに対して同じロジックを使う柔軟性も得られます。 イテレータがそれをする方法を調査しましょう。

Iteratorトレイトとnextメソッド

全てのイテレータは、標準ライブラリで定義されているIteratorというトレイトを実装しています。 このトレイトの定義は、以下のようになっています:


#![allow(unused)]
fn main() {
pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;

    // デフォルト実装のあるメソッドは省略
    // methods with default implementations elided
}
}

この定義は新しい記法を使用していることに注目してください: type ItemSelf::Itemで、 これらはこのトレイトとの関連型(associated type)を定義しています。関連型についての詳細は、第19章で語ります。 とりあえず、知っておく必要があることは、このコードがIteratorトレイトを実装するには、Item型も定義する必要があり、 そして、このItem型がnextメソッドの戻り値の型に使われていると述べていることです。換言すれば、 Item型がイテレータから返ってくる型になるだろうということです。

Iteratorトレイトは、一つのメソッドを定義することを実装者に要求することだけします: nextメソッドで、 これは1度にSomeに包まれたイテレータの1要素を返し、繰り返しが終わったら、Noneを返します。

イテレータに対して直接nextメソッドを呼び出すこともできます; リスト13-15は、 ベクタから生成されたイテレータのnextを繰り返し呼び出した時にどんな値が返るかを模擬しています。

ファイル名: src/lib.rs


#![allow(unused)]
fn main() {
#[test]
fn iterator_demonstration() {
    let v1 = vec![1, 2, 3];

    let mut v1_iter = v1.iter();

    assert_eq!(v1_iter.next(), Some(&1));
    assert_eq!(v1_iter.next(), Some(&2));
    assert_eq!(v1_iter.next(), Some(&3));
    assert_eq!(v1_iter.next(), None);
}
}

リスト13-15: イテレータに対してnextメソッドを呼び出す

v1_iterを可変にする必要があったことに注目してください: イテレータのnextメソッドを呼び出すと、 今シーケンスのどこにいるかを追いかけるためにイテレータが使用している内部の状態が変わります。 つまり、このコードはイテレータを消費、または使い込むのです。 nextの各呼び出しは、イテレータの要素を一つ、食います。forループを使用した時には、 v1_iterを可変にする必要はありませんでした。というのも、ループがv1_iterの所有権を奪い、 陰で可変にしていたからです。

また、nextの呼び出しで得られる値は、ベクタの値への不変な参照であることにも注目してください。 iterメソッドは、不変参照へのイテレータを生成します。v1の所有権を奪い、所有された値を返すイテレータを生成したいなら、 iterではなくinto_iterを呼び出すことができます。同様に、可変参照を繰り返したいなら、 iterではなくiter_mutを呼び出せます。

イテレータを消費するメソッド

Iteratorトレイトには、標準ライブラリが提供してくれているデフォルト実装のある多くの異なるメソッドがあります; Iteratorトレイトの標準ライブラリのAPIドキュメントを検索することで、これらのメソッドについて知ることができます。 これらのメソッドの中には、定義内でnextメソッドを呼ぶものもあり、故にIteratorトレイトを実装する際には、 nextメソッドを実装する必要があるのです。

nextを呼び出すメソッドは、消費アダプタ(consuming adaptors)と呼ばれます。呼び出しがイテレータの使い込みになるからです。 一例は、sumメソッドで、これはイテレータの所有権を奪い、nextを繰り返し呼び出すことで要素を繰り返し、 故にイテレータを消費するのです。繰り返しが進むごとに、各要素を一時的な合計に追加し、 繰り返しが完了したら、その合計を返します。リスト13-16は、sumの使用を説明したテストです:

ファイル名: src/lib.rs


#![allow(unused)]
fn main() {
#[test]
fn iterator_sum() {
    let v1 = vec![1, 2, 3];

    let v1_iter = v1.iter();

    let total: i32 = v1_iter.sum();

    assert_eq!(total, 6);
}
}

リスト13-16: sumメソッドを呼び出してイテレータの全要素の合計を得る

sumは呼び出し対象のイテレータの所有権を奪うので、sum呼び出し後にv1_iterを使用することはできません。

他のイテレータを生成するメソッド

Iteratorトレイトに定義された他のメソッドは、イテレータアダプタ(iterator adaptors)として知られていますが、 イテレータを別の種類のイテレータに変えさせてくれます。イテレータアダプタを複数回呼ぶ呼び出しを連結して、 複雑な動作を読みやすい形で行うことができます。ですが、全てのイテレータは怠惰なので、消費アダプタメソッドのどれかを呼び出し、 イテレータアダプタの呼び出しから結果を得なければなりません。

リスト13-17は、イテレータアダプタメソッドのmapの呼び出し例を示し、各要素に対して呼び出すクロージャを取り、 新しいイテレータを生成します。ここのクロージャは、ベクタの各要素が1インクリメントされる新しいイテレータを作成します。 ところが、このコードは警告を発します:

ファイル名: src/main.rs


#![allow(unused)]
fn main() {
let v1: Vec<i32> = vec![1, 2, 3];

v1.iter().map(|x| x + 1);
}

リスト13-17: イテレータアダプタのmapを呼び出して新規イテレータを作成する

出る警告は以下の通りです:

warning: unused `std::iter::Map` which must be used: iterator adaptors are lazy
and do nothing unless consumed
(警告: 使用されねばならない`std::iter::Map`が未使用です: イテレータアダプタは怠惰で、
消費されるまで何もしません)
 --> src/main.rs:4:5
  |
4 |     v1.iter().map(|x| x + 1);
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: #[warn(unused_must_use)] on by default

リスト13-17のコードは何もしません; 指定したクロージャは、決して呼ばれないのです。警告が理由を思い出させてくれています: イテレータアダプタは怠惰で、ここでイテレータを消費する必要があるのです。

これを修正し、イテレータを消費するには、collectメソッドを使用しますが、これは第12章のリスト12-1でenv::argsとともに使用しました。 このメソッドはイテレータを消費し、結果の値をコレクションデータ型に集結させます。

リスト13-18において、map呼び出しから返ってきたイテレータを繰り返した結果をベクタに集結させています。 このベクタは、最終的に元のベクタの各要素に1を足したものが含まれます。

ファイル名: src/main.rs


#![allow(unused)]
fn main() {
let v1: Vec<i32> = vec![1, 2, 3];

let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();

assert_eq!(v2, vec![2, 3, 4]);
}

リスト13-18: mapメソッドを呼び出して新規イテレータを作成し、 それからcollectメソッドを呼び出してその新規イテレータを消費し、ベクタを生成する

mapはクロージャを取るので、各要素に対して行いたいどんな処理も指定することができます。 これは、Iteratorトレイトが提供する繰り返し動作を再利用しつつ、 クロージャにより一部の動作をカスタマイズできる好例になっています。

環境をキャプチャするクロージャを使用する

イテレータが出てきたので、filterイテレータアダプタを使って環境をキャプチャするクロージャの一般的な使用をデモすることができます。 イテレータのfilterメソッドは、イテレータの各要素を取り、論理値を返すクロージャを取ります。 このクロージャがtrueを返せば、filterが生成するイテレータにその値が含まれます。クロージャがfalseを返したら、 結果のイテレータにその値は含まれません。

リスト13-19では、環境からshoe_size変数をキャプチャするクロージャでfilterを使って、 Shoe構造体インスタンスのコレクションを繰り返しています。指定したサイズの靴だけを返すわけです。

ファイル名: src/lib.rs


#![allow(unused)]
fn main() {
#[derive(PartialEq, Debug)]
struct Shoe {
    size: u32,
    style: String,
}

fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
    shoes.into_iter()
        .filter(|s| s.size == shoe_size)
        .collect()
}

#[test]
fn filters_by_size() {
    let shoes = vec![
        Shoe { size: 10, style: String::from("sneaker") },
        Shoe { size: 13, style: String::from("sandal") },
        Shoe { size: 10, style: String::from("boot") },
    ];

    let in_my_size = shoes_in_my_size(shoes, 10);

    assert_eq!(
        in_my_size,
        vec![
            Shoe { size: 10, style: String::from("sneaker") },
            Shoe { size: 10, style: String::from("boot") },
        ]
    );
}
}

リスト13-19: shoe_sizeをキャプチャするクロージャでfilterメソッドを使用する

shoes_in_my_size関数は、引数として靴のベクタとサイズの所有権を奪います。指定されたサイズの靴だけを含むベクタを返します。

shoes_in_my_sizeの本体で、into_iterを呼び出してベクタの所有権を奪うイテレータを作成しています。 そして、filterを呼び出してそのイテレータをクロージャがtrueを返した要素だけを含む新しいイテレータに適合させます。

クロージャは、環境からshoe_size引数をキャプチャし、指定されたサイズの靴だけを保持しながら、 その値を各靴のサイズと比較します。最後に、collectを呼び出すと、 関数により返ってきたベクタに適合させたイテレータから返ってきた値が集まるのです。

shoes_in_my_sizeを呼び出した時に、指定した値と同じサイズの靴だけが得られることをテストは示しています。

Iteratorトレイトで独自のイテレータを作成する

ベクタに対し、iterinto_iteriter_mutを呼び出すことでイテレータを作成できることを示してきました。 ハッシュマップなどの標準ライブラリの他のコレクション型からもイテレータを作成できます。 Iteratorトレイトを自分で実装することで、したいことを何でもするイテレータを作成することもできます。 前述の通り、定義を提供する必要のある唯一のメソッドは、nextメソッドなのです。一旦、そうしてしまえば、 Iteratorトレイトが用意しているデフォルト実装のある他の全てのメソッドを使うことができるのです!

デモ用に、絶対に1から5をカウントするだけのイテレータを作成しましょう。まず、値を保持する構造体を生成し、 Iteratorトレイトを実装することでこの構造体をイテレータにし、その実装内の値を使用します。

リスト13-20は、Counter構造体とCounterのインスタンスを作るnew関連関数の定義です:

ファイル名: src/lib.rs


#![allow(unused)]
fn main() {
struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}
}

リスト13-20: Counter構造体とcountに対して0という初期値でCounterのインスタンスを作るnew関数を定義する

Counter構造体には、countというフィールドがあります。このフィールドは、 1から5までの繰り返しのどこにいるかを追いかけるu32値を保持しています。Counterの実装にその値を管理してほしいので、 countフィールドは非公開です。countフィールドは常に0という値から新規インスタンスを開始するという動作をnew関数は強要します。

次に、nextメソッドの本体をこのイテレータが使用された際に起きてほしいことを指定するように定義して、 Counter型に対してIteratorトレイトを実装します。リスト13-21のようにですね:

ファイル名: src/lib.rs


#![allow(unused)]
fn main() {
struct Counter {
    count: u32,
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        self.count += 1;

        if self.count < 6 {
            Some(self.count)
        } else {
            None
        }
    }
}
}

リスト13-21: Counter構造体にIteratorトレイトを実装する

イテレータのItem関連型をu32に設定しました。つまり、イテレータは、u32の値を返します。 ここでも、まだ関連型について心配しないでください。第19章で講義します。

イテレータに現在の状態に1を足してほしいので、まず1を返すようにcountを0に初期化しました。 countの値が5以下なら、nextSomeに包まれた現在の値を返しますが、 countが6以上なら、イテレータはNoneを返します。

Counterイテレータのnextメソッドを使用する

一旦Iteratorトレイトを実装し終わったら、イテレータの出来上がりです!リスト13-22は、 リスト13-15のベクタから生成したイテレータと全く同様に、直接nextメソッドを呼び出すことで、 Counter構造体のイテレータ機能を使用できることをデモするテストを示しています。

ファイル名: src/lib.rs


#![allow(unused)]
fn main() {
struct Counter {
    count: u32,
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        self.count += 1;

        if self.count < 6 {
            Some(self.count)
        } else {
            None
        }
    }
}

#[test]
fn calling_next_directly() {
    let mut counter = Counter::new();

    assert_eq!(counter.next(), Some(1));
    assert_eq!(counter.next(), Some(2));
    assert_eq!(counter.next(), Some(3));
    assert_eq!(counter.next(), Some(4));
    assert_eq!(counter.next(), Some(5));
    assert_eq!(counter.next(), None);
}
}

リスト13-22: nextメソッド実装の機能をテストする

このテストは、counter変数に新しいCounterインスタンスを生成し、 それからイテレータにほしい動作が実装し終わっていることを実証しながら、nextを繰り返し呼び出しています: 1から5の値を返すことです。

他のIteratorトレイトメソッドを使用する

nextメソッドを定義してIteratorトレイトを実装したので、今では、標準ライブラリで定義されているように、 どんなIteratorトレイトメソッドのデフォルト実装も使えるようになりました。全てnextメソッドの機能を使っているからです。

例えば、何らかの理由で、Counterインスタンスが生成する値を取り、最初の値を飛ばしてから、 別のCounterインスタンスが生成する値と一組にし、各ペアを掛け算し、3で割り切れる結果だけを残し、 全結果の値を足し合わせたくなったら、リスト13-23のテストに示したように、そうすることができます:

ファイル名: src/lib.rs


#![allow(unused)]
fn main() {
struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

impl Iterator for Counter {
    // このイテレータはu32を生成します
    // Our iterator will produce u32s
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        // カウントをインクリメントする。故に0から始まる
        // increment our count. This is why we started at zero.
        self.count += 1;

        // カウントが終わったかどうか確認する
        // check to see if we've finished counting or not.
        if self.count < 6 {
            Some(self.count)
        } else {
            None
        }
    }
}

#[test]
fn using_other_iterator_trait_methods() {
    let sum: u32 = Counter::new().zip(Counter::new().skip(1))
                                 .map(|(a, b)| a * b)
                                 .filter(|x| x % 3 == 0)
                                 .sum();
    assert_eq!(18, sum);
}
}

リスト13-23: Counterイテレータに対していろんなIteratorトレイトのメソッドを使用する

zipは4組しか生成しないことに注意してください; 理論的な5番目の組の(5, None)は、 入力イテレータのどちらかがNoneを返したら、zipNoneを返却するため、決して生成されることはありません。

nextメソッドの動作方法を指定し、標準ライブラリがnextを呼び出す他のメソッドにデフォルト実装を提供しているので、 これらのメソッド呼び出しは全て可能です。

関連キーワード:  メソッド, next, iter, Counter, リスト, 要素, count, 実装, 生成, size