イテレータとは
イテレータは連続したコレクション(配列、ベクタ、ハッシュマップなど)を走査するためのメカニズムで、インデックスを使用せずに要素を処理できます。
forループとイテレータ
forループとイテレータは似ていますが、重要な違いがあります。forループではインデックスを使用して要素にアクセスしますが、イテレータはコレクション全体を直接走査します。
JavaScriptでの例:
let arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
Rustでの例:
let arr = [1, 2, 3];
for v in arr {
println!("{}", v);
}
Rustのforループは実際にはイテレータを使用しており、コンパイラによって自動的に変換されます。配列そのものはイテレータではありませんが、
IntoIteratorトレイトを実装することでイテレータとして扱うことができます。
範囲を使ったイテレーションも一般的です:
for i in 1..10 {
println!("{}", i);
}
イテレータの遅延初期化
イテレータは遅延初期化されます。つまり、イテレータを作成してもすぐに要素を走査しません。要素の消費はイテレータを使用したときに初めて行われます。
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
for val in v1_iter {
println!("{}", val);
}
nextメソッド
イテレータの要素を取得するために
nextメソッドを使用します。
nextメソッドは
Option型を返し、コレクションの終わりに達すると
Noneを返します。
Iteratorトレイトの定義:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option;
}
手動でイテレータを使用する例:
fn main() {
let arr = [1, 2, 3];
let mut arr_iter = arr.into_iter();
assert_eq!(arr_iter.next(), Some(1));
assert_eq!(arr_iter.next(), Some(2));
assert_eq!(arr_iter.next(), Some(3));
assert_eq!(arr_iter.next(), None);
}
IntoIterator トレイト
IntoIteratorトレイトはコレクションをイテレータに変換します。イテレータ自体もこのトレイトを実装しています。
impl IntoIterator for I {
type Item = I::Item;
type IntoIter = I;
fn into_iter(self) -> I {
self
}
}
into_iter, iter, iter_mut
これらのメソッドはコレクションをイテレータに変換しますが、それぞれ異なるアクセス権限を持ちます。
-
into_iter: 所有権を移動
-
iter: 不変な参照
-
iter_mut: 可変な参照
例:
fn main() {
let values = vec![1, 2, 3];
for v in values.into_iter() {
println!("{}", v)
}
let values = vec![1, 2, 3];
let _values_iter = values.iter();
println!("{:?}", values);
let mut values = vec![1, 2, 3];
let mut values_iter_mut = values.iter_mut();
if let Some(v) = values_iter_mut.next() {
*v = 0;
}
println!("{:?}", values);
}
消費者とアダプタ
イテレータの消費者はイテレータの要素を消費し、結果を返します。アダプタは新しいイテレータを生成します。
消費者アダプタ
sumメソッドはイテレータの要素を集計します。
fn main() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
let total: i32 = v1_iter.sum();
assert_eq!(total, 6);
}
イテレータアダプタ
mapメソッドはイテレータの各要素に対して関数を適用します。
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]);
クロージャーとしてのアダプターパラメータ
クロージャーはイテレータアダプターのパラメータとして使用され、環境から値をキャプチャできます。
例:
struct Shoe {
size: u32,
style: String,
}
fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}
Iterator トレイトの実装
独自のイテレータを作成するには、
Iteratorトレイトを実装します。
例:
struct Counter {
count: u32,
}
impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option {
if self.count < 5 {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
イテレータのパフォーマンス
イテレータはゼロコスト抽象であり、ランタイムオーバーヘッドを追加せずに効率的に動作します。
イテレータアダプタと消費者アダプタの一覧
イテレータアダプタ
map()
filter()
zip()
enumerate()
chain()
take()
skip()
inspect()
cycle()
消費者アダプタ
next()
count()
sum()
product()
fold()
reduce()
last()
any()
all()
find()
for_each()
collect()