Rust入門

【Rust入門】イテレータ(Iterator)の基本を分かりやすく解説

【Rust入門】イテレータ(Iterator)の基本を分かりやすく解説
naoki-hn

Rust におけるイテレータ(Iterator)の基本を初心者にも分かりやすく解説します。

イテレータとは

イテレータ(Iterator)は、値の列を生成する値で、要素を順番に取り出すことができる仕組みです。Rust では、文字列や Vec などから要素を取り出して処理するイテレータが用意されています。他にも、様々な場面でイテレータの仕組みが利用されています。

Rust の for 文はイテレータにとって自然に扱える構文となっており、シンプルなプログラム例は以下のようなものです。

fn main() {
    let v = vec![1, 2, 3];

    for x in v {
        println!("{}", x);
    }
}
【実行結果】
1
2
3

Rust では、このような繰り返し処理の多くがイテレータを使って実装されており、Rust における重要な仕組みです。Rust のイテレータは、具体的には、Iterator トレイトや IntoIterator トレイトにより表現されています。

この記事では、Rust のイテレータ(Iterator)の基本を解説します。

この記事では、トレイトに関する基本知識が必要です。トレイトについては「トレイトの基本を分かりやすく解説」を参考にしてください。

イテレータの基本

イテレータを実現する中心的なトレイトは、Iterator トレイトと IntoIterator トレイトです。ここでは、それぞれの概要について紹介します。

Iterator トレイト

Iterator トレイトは、以下のように定義されています。

pub trait Iterator {
    type Item;

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

    ...(他のメソッドは省略)...
}

Item という取り出す要素の型と next メソッドを持ちます。独自の型に Iterator トレイトを実装する際には next メソッドは必須のメソッドになります。他にもトレイトで定義されているメソッドはありますが、明確に定義しない場合はデフォルト実装が使用されます。

Iterator トレイトの詳細については、公式ドキュメントのこちらを参照してください。

next メソッド

next メソッドは、イテレータの中心となる重要なメソッドです。

fn main() {
    let v = vec![1, 2, 3];
    let mut iter = v.iter();

    println!("{:?}", iter.next()); // Some(1)
    println!("{:?}", iter.next()); // Some(2)
    println!("{:?}", iter.next()); // Some(3)
    println!("{:?}", iter.next()); // None
}
【実行結果】
Some(1)
Some(2)
Some(3)
None

iter メソッドは、対象のイテレータを取得します。(iter については後ほど説明します。)

イテレータにおける next メソッドは、順に要素を取り出し、値 x がある場合は、Option 型の Some(x) を返却します。要素を取り出し続け、取り出す要素がなくなったら最後に None を返却します。 イテレータは、Option 型を使って処理の終了を表現していることがポイントです。

v は、イミュータブル(変更不可)ですが、itermut を付けてミュータブル(変更可能)にしています。これは、next() メソッドがイテレータ自身の状態を更新するためであり、イテレータは mut で宣言する必要があります。

IntoIterator トレイト

IntoIterator トレイトは、イテレータを生成できる型を表すトレイトで、以下のように定義されています。

pub trait IntoIterator {
    type Item;
    type IntoIter: Iterator<Item = Self::Item>;

    // Required method
    fn into_iter(self) -> Self::IntoIter;
}

IntoIterator を実装している型は、into_iter メソッドによってイテレータに変換することができます。

IntoIterator トレイトの詳細については、公式ドキュメントのこちらを参照してください。

into_iter メソッド

into_iter メソッドは、値や参照からイテレータを生成するメソッドです。into_iter() で理解しておくべきことは以下の通りです。対象 v に対してそれぞれ呼ばれる実装が変わります。

  • (&v).into_iter():不変参照のイテレータを生成する
  • (&mut v).into_iter():可変参照のイテレータを生成する
  • v.into_iter():所有権を移動するイテレータを生成する

以下の例を見てみましょう。

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

    // 不変参照のイテレータを作成
    for x in (&v1).into_iter() {
        println!("{}", x);
    }

    let mut v2 = vec![4, 5, 6];

    // 可変参照のイテレータを作成
    for x in (&mut v2).into_iter() {
        *x += 1; // 各要素に1を加える
        println!("{}", x);
    }

    let v3 = vec![7, 8, 9];

    // 所有権を移動するイテレータを作成
    for x in v3.into_iter() {
        println!("{}", x);
    }

    // v3は所有権が移動したため、ここで使用できない
    // println!("{:?}", v3); // コンパイルエラー
}
【実行結果】
1
2
3
5
6
7
7
8
9

このように、into_iter() は同じ名前のメソッドであっても、値に対して呼ぶのか、参照に対して呼ぶのかで返すイテレータの型が異なります。この違いは、IntoIterator が「値」「不変参照」「可変参照」のそれぞれに対して実装されていることによるものです。そのため、要素の型も T / &T / &mut T のように変化します。なお、T は型パラメータです。

for 文と Iterator の関係

Rust の for 文は、IntoIterator トレイトを実装している型に対して使用できます。つまり、for 文は内部的に into_iter() を呼び出し、イテレータとして要素を取り出しています。

for 文で繰り返し処理を実装する場合は、明示的に into_iter() を書かなくても、以下のように不変参照で繰り返し処理を行うことができます。なお、可変参照や所有権移動の場合も同様に考えることができます。

for x in &v {
    println!("{}", x);
}

これは、内部的には、概ね次のような処理が行われていると考えることができます。

fn main() {
    let v = vec![1, 2, 3];

    let mut iter = (&v).into_iter();

    while let Some(x) = iter.next() {
        println!("{}", x);
    }
}

つまり、「for x in &v」は、(&v).into_iter() を使ってイテレータを生成し、next() で要素を順番に取り出して処理をしているというイメージです。

ここで示したコードは概念的な説明であり、Rust の内部実装が必ずしもこの通りであることを示すものではない点に注意してください。

for 文の基本については「繰り返し処理(for, while, loop)を分かりやすく解説」を参考にしてください。

コレクションにおける iteriter_mut メソッド

Vec などのコレクションでは、Iterator を返すメソッドとして、iter メソッドや iter_mut メソッドが提供されています。名称の通り、iter メソッドは不変参照のイテレータを、iter_mut メソッドは、可変参照のイテレータを返却します。

以下の例で見てみましょう。

fn main() {
    let mut v = vec![1, 2, 3];

    // iter メソッドで不変参照のイテレータを返却
    for x in v.iter() {
        println!("{}", x);

        // 不変参照なので、以下のように値を変更することはできない
        // *x += 1;
    }

    // iter_mut メソッドで可変参照のイテレータを返却
    for x in v.iter_mut() {
        *x += 1;
        println!("{}", x);
    }
}
【実行結果】
1
2
3
2
3
4

例えば、iter を使用すると要素への不変参照を順番に取り出すことができます。不変参照なので、値を変更しようとするとコンパイルエラーとなります。一方で、iter_mut を使用すると要素への可変参照を取得できるため、要素の値を書き換えることができます。

ポイント

多くのコレクションでは以下のような対応関係があります。

  • v.iter()(&v).into_iter() に対応
  • v.iter_mut()(&mut v).into_iter() に対応

つまり、iteriter_mut は、参照に対する into_iter() をより分かりやすく使うためのメソッドと考えることができます。ただし、誤解しやすいポイントですが、iteriter_mut は、Iterator トレイトや IntoIterator トレイトで定義されているメソッドではありません。

これらは、Rust の標準ライブラリにおいて、コレクションがイテレータを生成するための便利なメソッドとして提供しているものです。そのため、必ずしも上記対応が保証されているわけではありませんので注意してください。

Iterator トレイトの必須メソッドとされている nextIntoIterator トレイトの必須メソッドとされている into_iter とは位置づけが異なることを理解しておきましょう。

まとめ

Rust におけるイテレータ(Iterator)の基本を解説しました。

イテレータは、値の列を生成する値で、要素を順番に取り出すことができる仕組みで for 文などの繰り返し処理で利用されます。

イテレータの中心となるのが、Iterator トレイトと IntoIterator トレイトです。この記事では、それぞれの必須メソッドである nextinto_iter メソッドの役割について説明しました。また、for 文との関係や、Rust のコレクションで提供される iteriter_mut といったメソッドについても紹介しました。

イテレータは、Rust のデータ処理の中心的な仕組みです。基本の考え方をしっかりと理解しておきましょう。

ソースコード

上記で紹介しているソースコードについては GitHub にて公開しています。参考にしていただければと思います。

あわせて読みたい
Rust プログラミング入門
Rust プログラミング入門
ABOUT ME
ホッシー
ホッシー
システムエンジニア
はじめまして。当サイトをご覧いただきありがとうございます。
私は製造業のメーカーで、DX推進や業務システムの設計・開発・導入を担当しているシステムエンジニアです。これまでに転職も経験しており、以前は大手電機メーカーでシステム開発に携わっていました。

これまでの業務を通じてさまざまなプログラミング言語や技術に触れてきましたが、その中でもRustの設計思想に惹かれ、この言語についてもっと深く学びたい、そしてその魅力を発信していきたいと思い、このサイトを立ち上げました。

自身の学びを整理しつつ、同じようにRustに興味を持つ方のお役に立てるような情報を発信していければと思っています。どうぞよろしくお願いいたします。

※キャラクターデザイン:ゼイルン様
記事URLをコピーしました