第13章: イテレータ (Iterators)
学習目標
- Iterator トレイトの仕組みを理解する
- イテレータアダプタ(map、filter、collect等)を使いこなす
- ゼロコスト抽象化としてのイテレータを理解する
- カスタムイテレータを実装できるようになる
---
13.1 イテレータとは何か
13.1.1 イテレータの基本概念
イテレータは、シーケンスの要素を順番に処理する抽象化です。Rustのイテレータは:
┌─────────────────────────────────────────────────────────┐
│ Rustイテレータの特徴 │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. 遅延評価(Lazy Evaluation) │
│ └─ 必要になるまで計算しない │
│ │
│ 2. ゼロコスト抽象化 │
│ └─ 手書きループと同等の性能 │
│ │
│ 3. コンポーザブル(composable) │
│ └─ チェーン可能、組み合わせやすい │
│ │
│ 4. 関数型プログラミングスタイル │
│ └─ 不変性、宣言的 │
│ │
└─────────────────────────────────────────────────────────┘
13.1.2 基本的な使い方
fn main() {
let v = vec![1, 2, 3, 4, 5];
// 従来のforループ
for i in &v {
println!("{}", i);
}
// イテレータを使った方法
v.iter().for_each(|x| println!("{}", x));
// 実は、forループも内部でイテレータを使っている!
}
イテレータの生成方法:
let v = vec![1, 2, 3];
// 1. iter() - 不変参照のイテレータ
let iter1 = v.iter(); // Item = &i32
// 2. iter_mut() - 可変参照のイテレータ
let iter2 = v.iter_mut(); // Item = &mut i32
// 3. into_iter() - 所有権を移すイテレータ
let iter3 = v.into_iter(); // Item = i32
---
13.2 Iterator トレイト
13.2.1 Iterator トレイトの定義
pub trait Iterator {
type Item;
// 必須メソッド
fn next(&mut self) -> Option<Self::Item>;
// デフォルト実装を持つメソッド(一部)
fn count(self) -> usize { ... }
fn map<B, F>(self, f: F) -> Map<Self, F> { ... }
fn filter<P>(self, predicate: P) -> Filter<Self, P> { ... }
// ... 70以上のメソッド!
}
重要な点:
next()だけ実装すれば、他の70+メソッドが使えるOptionを返す(Noneで終了を表現)
13.2.2 手動でのイテレータ操作
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
println!("{:?}", iter.next()); // None (何度呼んでもNone)
}
13.2.3 簡単なカスタムイテレータ
// 1からnまでの数を生成するイテレータ
struct Counter {
current: u32,
max: u32,
}
impl Counter {
fn new(max: u32) -> Counter {
Counter { current: 0, max }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
self.current += 1;
if self.current <= self.max {
Some(self.current)
} else {
None
}
}
}
fn main() {
let counter = Counter::new(5);
for num in counter {
println!("{}", num); // 1, 2, 3, 4, 5
}
}
---
13.3 イテレータアダプタ
13.3.1 アダプタとコンシューマ
イテレータのメソッドは2種類に分類されます:
┌─────────────────────────────────────────────────────────┐
│ アダプタ vs コンシューマ │
├─────────────────────────────────────────────────────────┤
│ │
│ 【アダプタ (Adapter)】 │
│ - 新しいイテレータを返す │
│ - 遅延評価(チェーンするだけでは実行されない) │
│ - 例: map, filter, take, skip │
│ │
│ 【コンシューマ (Consumer)】 │
│ - 最終的な値を返す │
│ - 実際に計算を実行する │
│ - 例: collect, sum, count, for_each │
│ │
└─────────────────────────────────────────────────────────┘
13.3.2 主要なアダプタ
map - 変換
fn main() {
let v = vec![1, 2, 3];
let doubled: Vec<i32> = v.iter()
.map(|x| x * 2)
.collect();
println!("{:?}", doubled); // [2, 4, 6]
}
filter - フィルタリング
fn main() {
let v = vec![1, 2, 3, 4, 5, 6];
let evens: Vec<i32> = v.iter()
.filter(|x| *x % 2 == 0)
.copied() // &i32 を i32 に変換
.collect();
println!("{:?}", evens); // [2, 4, 6]
}
take / skip - 範囲制御
fn main() {
let v = vec![1, 2, 3, 4, 5];
let first_three: Vec<_> = v.iter().take(3).collect();
println!("{:?}", first_three); // [1, 2, 3]
let skip_two: Vec<_> = v.iter().skip(2).collect();
println!("{:?}", skip_two); // [3, 4, 5]
}
enumerate - インデックス付き
fn main() {
let v = vec!["a", "b", "c"];
for (i, val) in v.iter().enumerate() {
println!("{}: {}", i, val);
}
// 0: a
// 1: b
// 2: c
}
zip - 2つのイテレータを結合
fn main() {
let names = vec!["Alice", "Bob", "Charlie"];
let ages = vec![25, 30, 35];
let people: Vec<_> = names.iter()
.zip(ages.iter())
.collect();
println!("{:?}", people);
// [("Alice", 25), ("Bob", 30), ("Charlie", 35)]
}
13.3.3 チェーンの組み合わせ
fn main() {
let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let result: Vec<i32> = v.iter()
.filter(|x| *x % 2 == 0) // 偶数のみ
.map(|x| x * x) // 二乗
.take(3) // 最初の3つ
.collect();
println!("{:?}", result); // [4, 16, 36]
}
遅延評価の威力:
fn main() {
let v = vec![1, 2, 3, 4, 5];
// これだけでは何も実行されない!
let iter = v.iter()
.inspect(|x| println!("Processing: {}", x))
.map(|x| x * 2)
.filter(|x| *x > 5);
println!("Iterator created");
// collect() で初めて実行される
let result: Vec<_> = iter.collect();
println!("Result: {:?}", result);
}
// 出力:
// Iterator created
// Processing: 1
// Processing: 2
// Processing: 3
// Processing: 4
// Processing: 5
// Result: [6, 8, 10]
---
13.4 コンシューマメソッド
13.4.1 collect - 収集
use std::collections::HashMap;
fn main() {
let v = vec![1, 2, 3];
// Vec<i32>
let v1: Vec<i32> = v.iter().copied().collect();
// String
let chars = vec!['h', 'e', 'l', 'l', 'o'];
let s: String = chars.iter().collect();
// HashMap
let pairs = vec![("a", 1), ("b", 2)];
let map: HashMap<_, _> = pairs.into_iter().collect();
}
型推論の力:
fn main() {
let v = vec![1, 2, 3];
// 型注釈が必要(曖昧なため)
let result: Vec<i32> = v.iter().copied().collect();
// または、turbofish構文
let result = v.iter().copied().collect::<Vec<i32>>();
}
13.4.2 fold - 畳み込み
fn main() {
let v = vec![1, 2, 3, 4, 5];
// 合計
let sum = v.iter().fold(0, |acc, x| acc + x);
println!("Sum: {}", sum); // 15
// 積
let product = v.iter().fold(1, |acc, x| acc * x);
println!("Product: {}", product); // 120
// 文字列の結合
let words = vec!["Hello", "Rust", "World"];
let sentence = words.iter().fold(String::new(), |mut acc, word| {
if !acc.is_empty() {
acc.push(' ');
}
acc.push_str(word);
acc
});
println!("{}", sentence); // "Hello Rust World"
}
13.4.3 sum / product
fn main() {
let v = vec![1, 2, 3, 4, 5];
let sum: i32 = v.iter().sum();
println!("Sum: {}", sum); // 15
let product: i32 = v.iter().product();
println!("Product: {}", product); // 120
}
13.4.4 any / all
fn main() {
let v = vec![1, 2, 3, 4, 5];
let has_even = v.iter().any(|x| x % 2 == 0);
println!("Has even: {}", has_even); // true
let all_positive = v.iter().all(|x| *x > 0);
println!("All positive: {}", all_positive); // true
let all_even = v.iter().all(|x| x % 2 == 0);
println!("All even: {}", all_even); // false
}
13.4.5 find / position
fn main() {
let v = vec![1, 2, 3, 4, 5];
// 最初の偶数を見つける
let first_even = v.iter().find(|x| *x % 2 == 0);
println!("{:?}", first_even); // Some(2)
// 最初の偶数の位置
let pos = v.iter().position(|x| *x % 2 == 0);
println!("{:?}", pos); // Some(1)
}
---
13.5 ゼロコスト抽象化の実例
13.5.1 イテレータ vs ループの性能
fn sum_loop(v: &[i32]) -> i32 {
let mut sum = 0;
for i in 0..v.len() {
sum += v[i];
}
sum
}
fn sum_iterator(v: &[i32]) -> i32 {
v.iter().sum()
}
fn main() {
let v: Vec<i32> = (1..=1000000).collect();
use std::time::Instant;
let start = Instant::now();
let s1 = sum_loop(&v);
let elapsed1 = start.elapsed();
let start = Instant::now();
let s2 = sum_iterator(&v);
let elapsed2 = start.elapsed();
println!("Loop: {} in {:?}", s1, elapsed1);
println!("Iterator: {} in {:?}", s2, elapsed2);
// ほぼ同じ速度!
}
13.5.2 最適化されたコード
コンパイラが生成する機械語はほぼ同一:
// 高水準なコード
let sum: i32 = (1..=100).filter(|x| x % 2 == 0).sum();
// コンパイル後は以下と同等
let mut sum = 0;
let mut i = 1;
while i <= 100 {
if i % 2 == 0 {
sum += i;
}
i += 1;
}
---
13.6 高度なイテレータパターン
13.6.1 flat_map - フラット化
fn main() {
let words = vec!["hello", "world"];
// 各単語を文字のイテレータに変換してフラット化
let chars: String = words.iter()
.flat_map(|s| s.chars())
.collect();
println!("{}", chars); // "helloworld"
}
13.6.2 scan - 状態を持つmap
fn main() {
let v = vec![1, 2, 3, 4, 5];
// 累積和
let cumsum: Vec<i32> = v.iter()
.scan(0, |state, x| {
*state += x;
Some(*state)
})
.collect();
println!("{:?}", cumsum); // [1, 3, 6, 10, 15]
}
13.6.3 chain - イテレータの連結
fn main() {
let v1 = vec![1, 2, 3];
let v2 = vec![4, 5, 6];
let combined: Vec<i32> = v1.iter()
.chain(v2.iter())
.copied()
.collect();
println!("{:?}", combined); // [1, 2, 3, 4, 5, 6]
}
13.6.4 partition - 分割
fn main() {
let v = vec![1, 2, 3, 4, 5, 6];
let (evens, odds): (Vec<_>, Vec<_>) = v.iter()
.partition(|x| *x % 2 == 0);
println!("Evens: {:?}", evens); // [2, 4, 6]
println!("Odds: {:?}", odds); // [1, 3, 5]
}
---
13.7 カスタムイテレータの実装
13.7.1 Fibonacci イテレータ
struct Fibonacci {
curr: u64,
next: u64,
}
impl Fibonacci {
fn new() -> Self {
Fibonacci { curr: 0, next: 1 }
}
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
let current = self.curr;
self.curr = self.next;
self.next = current + self.next;
Some(current)
}
}
fn main() {
let fib = Fibonacci::new();
let first_10: Vec<u64> = fib.take(10).collect();
println!("{:?}", first_10);
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
}
13.7.2 範囲イテレータ
struct Range {
start: i32,
end: i32,
step: i32,
}
impl Range {
fn new(start: i32, end: i32, step: i32) -> Self {
Range { start, end, step }
}
}
impl Iterator for Range {
type Item = i32;
fn next(&mut self) -> Option<Self::Item> {
if self.start < self.end {
let current = self.start;
self.start += self.step;
Some(current)
} else {
None
}
}
}
fn main() {
let range = Range::new(0, 10, 2);
for num in range {
println!("{}", num); // 0, 2, 4, 6, 8
}
}
13.7.3 逆順イテレータ
struct ReverseVec<T> {
data: Vec<T>,
index: usize,
}
impl<T> ReverseVec<T> {
fn new(data: Vec<T>) -> Self {
let index = data.len();
ReverseVec { data, index }
}
}
impl<T: Clone> Iterator for ReverseVec<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
if self.index > 0 {
self.index -= 1;
Some(self.data[self.index].clone())
} else {
None
}
}
}
fn main() {
let v = vec![1, 2, 3, 4, 5];
let rev = ReverseVec::new(v);
for num in rev {
println!("{}", num); // 5, 4, 3, 2, 1
}
}
---
13.8 実践的なパターン
13.8.1 データ処理パイプライン
#[derive(Debug)]
struct Person {
name: String,
age: u32,
salary: u32,
}
fn main() {
let people = vec![
Person { name: "Alice".to_string(), age: 25, salary: 50000 },
Person { name: "Bob".to_string(), age: 30, salary: 60000 },
Person { name: "Charlie".to_string(), age: 35, salary: 70000 },
Person { name: "David".to_string(), age: 28, salary: 55000 },
];
// 30歳以上で給与が60000以上の人の名前
let qualified: Vec<String> = people.iter()
.filter(|p| p.age >= 30 && p.salary >= 60000)
.map(|p| p.name.clone())
.collect();
println!("{:?}", qualified); // ["Bob", "Charlie"]
}
13.8.2 CSV処理
fn main() {
let csv = "Name,Age,City\n\
Alice,25,Tokyo\n\
Bob,30,Osaka\n\
Charlie,35,Kyoto";
let data: Vec<Vec<&str>> = csv.lines()
.skip(1) // ヘッダーをスキップ
.map(|line| line.split(',').collect())
.collect();
for row in data {
println!("{:?}", row);
}
}
13.8.3 グループ化
use std::collections::HashMap;
fn main() {
let words = vec!["apple", "banana", "apricot", "blueberry", "avocado"];
// 頭文字でグループ化
let grouped: HashMap<char, Vec<&str>> = words.iter()
.fold(HashMap::new(), |mut acc, word| {
let first = word.chars().next().unwrap();
acc.entry(first).or_insert_with(Vec::new).push(*word);
acc
});
for (letter, group) in grouped {
println!("{}: {:?}", letter, group);
}
// a: ["apple", "apricot", "avocado"]
// b: ["banana", "blueberry"]
}
---
13.9 まとめ
イテレータの利点
┌─────────────────────────────────────────────────────────┐
│ イテレータを使うべき理由 │
├─────────────────────────────────────────────────────────┤
│ │
│ ✓ ゼロコスト抽象化 │
│ └─ 手書きループと同等の性能 │
│ │
│ ✓ 宣言的で読みやすい │
│ └─ 「何をするか」に集中できる │
│ │
│ ✓ コンポーザブル │
│ └─ 小さな部品を組み合わせて複雑な処理を構築 │
│ │
│ ✓ 安全 │
│ └─ インデックス範囲外エラーの心配なし │
│ │
│ ✓ 遅延評価 │
│ └─ 必要な分だけ計算 │
│ │
└─────────────────────────────────────────────────────────┘
次のステップ
次の章では、クロージャについて学びます。イテレータと組み合わせることで、さらに強力な表現が可能になります。
---