クロージャとイテレータは、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のクロージャとイテレータについて学びました。
クロージャを使うことで関数内で外部の変数をキャプチャし、柔軟な関数を記述することができます。
また、イテレータを利用して効率的な反復処理を行い、パフォーマンスを向上させることができます。
クロージャとイテレータを組み合わせることで、より強力なデータ操作が可能です。