Rustのクロージャとイテレータの使い方

クロージャとイテレータは、Rustにおいて柔軟で効率的なコードを書くための強力なツールです。

クロージャは関数のように振る舞いますが、周囲のスコープにある変数をキャプチャできる点で異なります。

一方、イテレータはコレクションを繰り返し処理する際に利用され、Rustでは効率的なメモリ管理と高いパフォーマンスを実現しています。

この記事では、Rustのクロージャとイテレータの基本を学び、それらを使って効率的なプログラムを構築する方法を解説します。


クロージャとは?

クロージャは、関数のように動作しますが、呼び出し元のスコープにある変数をキャプチャして使用できる点が特徴です。

Rustでは、クロージャは軽量で、型推論が行われるため、簡潔なコードが書けます。

クロージャの基本

クロージャは、|x| x + 1のようなシンプルな構文で定義されます。

以下のコードで基本的なクロージャの使い方を確認してみましょう。

fn main() {
    let add_one = |x: i32| -> i32 { x + 1 };
    let result = add_one(5);
    println!("結果: {}", result);
}

ポイント:

  • |x: i32| -> i32 は、xという引数を受け取り、x + 1 を返すクロージャです。
  • クロージャの型は省略することができます。Rustが型推論を行い、適切な型を推測してくれます。

クロージャとスコープ

クロージャは、外部の変数をキャプチャできます。

次の例では、クロージャがyという変数をキャプチャしています。

fn main() {
    let y = 10;
    let add_y = |x: i32| x + y;
    let result = add_y(5);
    println!("結果: {}", result); // 結果: 15
}

ポイント:

  • クロージャadd_yは、スコープ内の変数yをキャプチャし、x + yを計算しています。Rustでは、このキャプチャは自動的に行われます。

イテレータとは?

イテレータは、コレクション(例:配列やベクタ)を効率的に処理するための仕組みです。

Rustのイテレータは遅延評価を行い、必要なタイミングでのみ処理が実行されるため、メモリやパフォーマンスに優れています。

イテレータの基本

Rustのイテレータは、.iter() メソッドで作成され、for ループで簡単に繰り返し処理を行うことができます。

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    for num in numbers.iter() {
        println!("{}", num);
    }
}

ポイント:

  • vec![1, 2, 3, 4, 5] はベクタを作成し、.iter() でイテレータを生成しています。
  • for ループを使って、イテレータから要素を1つずつ取り出して処理しています。

イテレータメソッド

イテレータは、さまざまなメソッドを持ち、効率的にデータを操作できます。

次の例では、イテレータの .map().filter() メソッドを使っています。

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    let result: Vec<i32> = numbers.iter()
        .map(|x| x * 2)  // 各要素を2倍にする
        .filter(|x| *x > 5)  // 5より大きいものだけを残す
        .collect();  // 結果をベクタに収集する

    println!("{:?}", result); // [6, 8, 10]
}

ポイント:

  • .map() は各要素に関数を適用します。
  • .filter() は条件に合った要素だけを残します。
  • .collect() で結果をベクタなどのコレクションに収集します。

クロージャとイテレータの組み合わせ

クロージャとイテレータは非常に相性が良く、クロージャをイテレータのメソッドに渡して柔軟なデータ操作を行うことができます。

クロージャを使ったイテレータの操作

次の例では、イテレータのメソッドにクロージャを渡して処理を行っています。

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];
    let sum: i32 = numbers.iter()
        .map(|x| x * 2)  // 各要素を2倍にする
        .sum();  // 合計を計算する

    println!("合計: {}", sum); // 合計: 30
}

ポイント:

  • map() メソッドにクロージャ|x| x * 2を渡して、すべての要素を2倍にしています。
  • sum() メソッドで、すべての要素の合計を計算しています。

カスタムイテレータの実装

Rustでは独自のイテレータを作成することも可能です。

次の例では、独自のイテレータを実装しています。

struct Counter {
    count: u32,
}

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

impl Iterator for Counter {
    type Item = u32;

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

fn main() {
    let mut counter = Counter::new();

    while let Some(count) = counter.next() {
        println!("{}", count);
    }
}

ポイント:

  • Counter 構造体に Iterator トレイトを実装し、next() メソッドを定義しています。
  • next() メソッドが呼び出されるたびに、count の値をインクリメントし、5を超えたら None を返します。

実践例

クロージャとイテレータを組み合わせて、より実践的な例を見てみましょう。

次のコードでは、リストから条件に合った要素をクロージャでフィルタリングし、イテレータで処理しています。

fn main() {
    let scores = vec![10, 30, 50, 70, 90];
    let passed: Vec<i32> = scores.iter()
        .filter(|&&x| x >= 50)  // 50点以上を抽出
        .map(|&x| x + 5)  // ボーナスを加算
        .collect();  // 結果をベクタに収集

    println!("合格者のスコア: {:?}", passed);  // [55, 75, 95]
}

ポイント:

  • filter() にクロージャ |&&x| x >= 50 を渡して、50点以上のスコアをフィルタリング。
  • map() でボーナスを加算し、collect() で最終結果をベクタに収集しています。

練習問題

練習問題1: クロージャとイテレータの活用

次のコードを完成させ、リスト内の偶数だけを抽出し、2倍にした結果を表示してください。

fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8];

    let result: Vec<i32> = numbers.iter()
        .filter(|&&x| x % 2 == 0)  // 偶数を抽出
        .map(|&x| x * 2)  // 2倍にする
        .collect();

    // 結果を表示
}

まとめ

今回の記事では、Rustのクロージャとイテレータについて学びました。

クロージャを使うことで関数内で外部の変数をキャプチャし、柔軟な関数を記述することができます。

また、イテレータを利用して効率的な反復処理を行い、パフォーマンスを向上させることができます。

クロージャとイテレータを組み合わせることで、より強力なデータ操作が可能です。

次回予告のデザイン

次回予告

次回は「Rustの並行性:スレッドとメッセージパッシング」をテーマに、並行処理を行う方法を学びます。スレッドとメッセージパッシングを活用し、複数のタスクを効率よく処理するための基本を解説します。