🗺️
Rust イテレータ関連チートシート
はじめに
こんにちは、ファスト株式会社のyutakです!
今回は、Rustでよく使うイテレータの操作で調べながら使うことが多々ありましたので、
チートシート的な意味でまとめました。
対象読者
- Rustの基本的な文法を理解している人
- イテレータの使い方を学びたい人
- よく使うメソッドを手軽に確認したい人
1. イテレータの作成方法
iter()
- 借用イテレータ
要素を借用して処理します。元のデータは変更されません。
let numbers = vec![1, 2, 3];
let iter = numbers.iter(); // &i32を返す
into_iter()
- 所有権移譲イテレータ
要素の所有権を取得して処理します。元のデータは使用不可になります。
let numbers = vec![1, 2, 3];
let iter = numbers.into_iter(); // i32を返す
// この後、numbersは使用不可
iter_mut()
- 可変借用イテレータ
要素を可変借用して処理します。元のデータを変更できます。
let mut numbers = vec![1, 2, 3];
let iter = numbers.iter_mut(); // &mut i32を返す
3. 基本的な変換メソッド
map()
- 要素を変換
想定されるであろう使用場面: API レスポンスの変換、ファイルパス処理、データ正規化
let numbers = vec![1, 2, 3];
let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
// [2, 4, 6]
// 実用例:JSONから必要な情報だけ抽出
struct User { id: u32, name: String, email: String }
let users = vec![/* ... */];
let user_names: Vec<String> = users.iter()
.map(|user| user.name.clone())
.collect();
filter()
- 条件で絞り込み
想定されるであろう使用場面: ログ解析、データのクレンジング
let numbers = vec![1, 2, 3, 4, 5];
let even: Vec<i32> = numbers.iter().filter(|&x| x % 2 == 0).cloned().collect();
// [2, 4]
// 実用例:エラーログの抽出
let log_lines = vec![
"INFO: Application started",
"ERROR: Database connection failed",
"WARN: Memory usage high",
"ERROR: Invalid user input"
];
let errors: Vec<&str> = log_lines.iter()
.filter(|line| line.contains("ERROR"))
.cloned().collect();
enumerate()
- インデックス付き
想定されるであろう使用場面: CSVファイル処理、行番号付きエラー報告、テーブル表示
let fruits = vec!["apple", "banana", "cherry"];
for (index, fruit) in fruits.iter().enumerate() {
println!("{}: {}", index, fruit);
}
// 0: apple
// 1: banana
// 2: cherry
// 実用例:CSV解析時のエラー行特定
let csv_lines = vec!["name,age", "Alice,25", "Bob,invalid", "Charlie,30"];
let parse_errors: Vec<(usize, &str)> = csv_lines.iter().enumerate()
.skip(1) // ヘッダー行をスキップ
.filter(|(_, line)| {
let parts: Vec<&str> = line.split(',').collect();
parts.len() != 2 || parts[1].parse::<u32>().is_err()
})
.collect();
// [(2, "Bob,invalid")]
zip()
- 複数のイテレータを組み合わせ
想定されるであろう使用場面: データベース結合、設定ファイル処理、テスト結果比較
let names = vec!["Alice", "Bob", "Charlie"];
let ages = vec![25, 30, 35];
let pairs: Vec<(&str, i32)> = names.iter().zip(ages.iter()).map(|(name, age)| (*name, *age)).collect();
// [("Alice", 25), ("Bob", 30), ("Charlie", 35)]
// 実用例:テスト結果と期待値の比較
let actual_results = vec!["pass", "fail", "pass"];
let expected_results = vec!["pass", "pass", "pass"];
let test_summary: Vec<(usize, bool)> = actual_results.iter()
.zip(expected_results.iter())
.enumerate()
.map(|(i, (actual, expected))| (i, actual == expected))
.collect();
// [(0, true), (1, false), (2, true)]
4. 集約・終端メソッド
collect()
- コレクションに変換
let numbers = vec![1, 2, 3];
let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
fold()
/ reduce()
- 累積処理
想定されるであろう使用場面: 売上集計、ファイルサイズ計算、設定値マージ
let numbers = vec![1, 2, 3, 4, 5];
// fold() - 初期値を指定
let sum = numbers.iter().fold(0, |acc, x| acc + x);
// 15
// reduce() - 初期値なし
let sum = numbers.iter().reduce(|acc, x| acc + x);
// Some(15)
// 実用例:売上データの集計
struct Sale { amount: f64, tax: f64 }
let sales = vec![
Sale { amount: 100.0, tax: 10.0 },
Sale { amount: 200.0, tax: 20.0 },
Sale { amount: 150.0, tax: 15.0 },
];
let total_revenue = sales.iter().fold(0.0, |acc, sale| acc + sale.amount + sale.tax);
// 495.0
for_each()
- 各要素に処理実行
let numbers = vec![1, 2, 3];
numbers.iter().for_each(|x| println!("{}", x));
count()
- 要素数をカウント
let numbers = vec![1, 2, 3, 4, 5];
let even_count = numbers.iter().filter(|&x| x % 2 == 0).count();
// 2
5. 検索・条件判定
find()
- 条件に合う最初の要素
想定されるであろう使用場面: 設定値検索、ユーザー認証、ファイル検索
let numbers = vec![1, 2, 3, 4, 5];
let first_even = numbers.iter().find(|&x| x % 2 == 0);
// Some(&2)
// 実用例:設定ファイルから特定の値を探す
struct Config { key: String, value: String }
let configs = vec![
Config { key: "host".to_string(), value: "localhost".to_string() },
Config { key: "port".to_string(), value: "8080".to_string() },
Config { key: "debug".to_string(), value: "true".to_string() },
];
let port_config = configs.iter().find(|config| config.key == "port");
if let Some(config) = port_config {
println!("Port: {}", config.value);
}
any()
/ all()
- 条件判定
想定されるであろう使用場面: 権限チェック、フォームバリデーション、ヘルスチェック
let numbers = vec![2, 4, 6, 8];
let has_even = numbers.iter().any(|&x| x % 2 == 0);
// true
let all_even = numbers.iter().all(|&x| x % 2 == 0);
// true
// 実用例:サーバーヘルスチェック
struct Service { name: String, status: String }
let services = vec![
Service { name: "web".to_string(), status: "running".to_string() },
Service { name: "db".to_string(), status: "running".to_string() },
Service { name: "cache".to_string(), status: "stopped".to_string() },
];
let all_healthy = services.iter().all(|s| s.status == "running");
let has_issues = services.iter().any(|s| s.status != "running");
// all_healthy: false, has_issues: true
max()
/ min()
- 最大値・最小値
想定されるであろう使用場面: パフォーマンス分析、価格比較、リソース監視
let numbers = vec![3, 1, 4, 1, 5];
let max_value = numbers.iter().max();
// Some(&5)
let min_value = numbers.iter().min();
// Some(&1)
// 実用例:レスポンス時間の分析
struct ApiCall { endpoint: String, response_time_ms: u64 }
let api_calls = vec![
ApiCall { endpoint: "/users".to_string(), response_time_ms: 120 },
ApiCall { endpoint: "/orders".to_string(), response_time_ms: 250 },
ApiCall { endpoint: "/products".to_string(), response_time_ms: 180 },
];
let slowest = api_calls.iter().max_by_key(|call| call.response_time_ms);
let fastest = api_calls.iter().min_by_key(|call| call.response_time_ms);
// slowest: /orders (250ms), fastest: /users (120ms)
6. 実践的な組み合わせパターン
よく使うメソッドチェーンの例
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 偶数を2倍にして合計を求める
let result = numbers
.iter()
.filter(|&x| x % 2 == 0)
.map(|x| x * 2)
.sum::<i32>();
// 60
// 文字列を変換してフィルタリング
let words = vec!["hello", "world", "rust", "programming"];
let long_words: Vec<String> = words
.iter()
.filter(|word| word.len() > 4)
.map(|word| word.to_uppercase())
.collect();
// ["HELLO", "WORLD", "PROGRAMMING"]
実用的なコード例
// CSVライクなデータの処理
let data = vec![
"Alice,25,Engineer",
"Bob,30,Designer",
"Charlie,35,Manager",
];
let adults: Vec<String> = data
.iter()
.map(|line| line.split(',').collect::<Vec<&str>>())
.filter(|parts| parts[1].parse::<i32>().unwrap_or(0) >= 30)
.map(|parts| format!("{} ({})", parts[0], parts[2]))
.collect();
// ["Bob (Designer)", "Charlie (Manager)"]
7. 覚えておきたいTips
パフォーマンスのポイント
- イテレータは遅延評価なので、
collect()
等の終端メソッドが呼ばれるまで実行されない - メソッドチェーンは最適化されるため、中間コレクションを作るより効率的
// 良い例:メソッドチェーン
let result: Vec<i32> = numbers.iter().filter(|&x| x > 0).map(|x| x * 2).collect();
// 避けるべき例:中間コレクション
let filtered: Vec<i32> = numbers.iter().filter(|&x| x > 0).cloned().collect();
let doubled: Vec<i32> = filtered.iter().map(|x| x * 2).collect();
所有権でハマりやすい点
-
iter()
は参照を返すので、値が必要な場合はcloned()
やcopied()
を使用 -
into_iter()
を使うと元のデータが使用不可になる
let numbers = vec![1, 2, 3];
// 参照のイテレータ
let doubled_refs: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
// 値のイテレータ(cloned()を使用)
let doubled_values: Vec<i32> = numbers.iter().cloned().map(|x| x * 2).collect();
// 所有権移譲
let doubled_owned: Vec<i32> = numbers.into_iter().map(|x| x * 2).collect();
// この後、numbersは使用不可
まとめ
Rustのイテレータは強力で表現力豊かな機能です。メソッドチェーンを使うことで、関数型プログラミングのスタイルで効率的にデータを処理できます。
この記事が、Rustでのイテレータ活用の参考になれば幸いです!
Discussion