反復子を使用する

完了

ループを使用してコレクション型を反復処理する方法については既に学習しました。 今回は、Rust で反復自体の概念を処理する方法について、さらに掘り下げて確認します。

Rust では、すべての反復子で Iterator という名前の特性が実装されます。これは、標準ライブラリに定義されていて、範囲、配列、ベクター、ハッシュ マップなどのコレクションに反復子を実装する場合に使用されます。

この特性の核となる部分は次のようになります。

trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

Iterator には next というメソッドがあります。これを呼び出すと、Option<Item> が返されます。 next メソッドからは、要素がある限り、Some(Item) が返されます。 すべての処理が完了すると、反復が終了したことを示す None が返されます。

この定義には、この特性に関連付けられた型を定義する type Item および Self::Item という新しい構文が使用されていることに注目してください。 この定義は、Iterator 特性のすべての実装には、関連付けられた Item 型の定義も必要であることを意味しています。これは next メソッドの戻り値の型として使用されます。 言い換えると、Item 型は、for ループ ブロック内の反復子から返される型になります。

独自の反復子を実装する

独自の反復子を作成するには、次の 2 つの手順が必要です。

  1. 反復子の状態を保持する構造体を作成します。
  2. その構造体に対して反復子を実装します。

1 から任意の数 (Counter 構造体を作成するときに定義) までをカウントする Counter という名前の反復子を作成してみましょう。

最初に、反復子の状態を保持する構造体を作成します。 また、new メソッドも実装して、開始する方法を制御します。

#[derive(Debug)]
struct Counter {
    length: usize,
    count: usize,
}

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

次に、Counter 構造体の Iterator 特性を実装します。 usize を使用してカウントすることになるので、関連付けられた Item 型をその型とすることを宣言します。

next() メソッドは、定義する必要がある唯一の必須メソッドです。 その本体内では、呼び出しのたびにカウントが 1 つずつインクリメントされます "(これがゼロから始めた理由です)"。 次に、カウントが終了したかどうかを確認します。 反復によって引き続き結果が生成されることを表するには、Option 型の Some(value) バリアントを使用し、反復が停止される必要があることを表すには None バリアントを使用します。

impl Iterator for Counter {
    type Item = usize;

    fn next(&mut self) -> Option<Self::Item> {
    
        self.count += 1;
        if self.count <= self.length {
            Some(self.count)
        } else {
            None
        }
    }
}

Counter が機能することを確認するには、その next 関数を明示的に呼び出します。

fn main() {
    let mut counter = Counter::new(6);
    println!("Counter just created: {:#?}", counter);

    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(), Some(6));
    assert_eq!(counter.next(), None);
    assert_eq!(counter.next(), None);  // further calls to `next` will return `None`
    assert_eq!(counter.next(), None);

    println!("Counter exhausted: {:#?}", counter);
}

しかし、この方法で next を呼び出すと、繰り返しになります。 Rust を使用すると、Iterator 特性を実装する型に for ループを使用することができるので、次のようにしてみましょう。

fn main() {
    for number in Counter::new(10) {
        println!("{}", number);
    }
}

上記のスニペットを実行すると、コンソールに次の出力が出力されます。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

Iterator 特性の完全な定義には他のメソッドも含まれていますが、それらは既定のメソッドです。 next を基にして作成されているので、無料で入手できます。

let sum_until_10: usize = Counter::new(10).sum();
assert_eq!(sum_until_10, 55);

let powers_of_2: Vec<usize> = Counter::new(8).map(|n| 2usize.pow(n as u32)).collect();
assert_eq!(powers_of_2, vec![2, 4, 8, 16, 32, 64, 128, 256]);

このユニットの完全なコード例については、こちらの Rust Playground のリンクをご覧ください。