🗺️

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でのイテレータ活用の参考になれば幸いです!

GitHubで編集を提案
FAST Tech Blog

Discussion