クロージャとは何か
Rustのクロージャ(closure)は、変数に束縛したり引数として渡したりできる「その場で定義できる関数」です。Pythonのラムダ式やJavaScriptのアロー関数に近いイメージです。
基本的な構文は次のとおりです。
fn main() {
// 通常の関数
fn add(x: i32, y: i32) -> i32 {
x + y
}
// クロージャ(型推論が効くので型注釈を省略できる)
let add_closure = |x, y| x + y;
println!("{}", add(2, 3)); // 5
println!("{}", add_closure(2, 3)); // 5
}
クロージャが通常の関数と大きく違う点は、周囲のスコープの変数をキャプチャ(捕捉)できることです。
fn main() {
let offset = 10;
// offset は外側のスコープの変数だが、クロージャの中で使える
let add_offset = |x| x + offset;
println!("{}", add_offset(5)); // 15
println!("{}", add_offset(20)); // 30
}
クロージャのキャプチャモード
クロージャが外部変数をキャプチャするとき、Rustは状況に応じて自動的にキャプチャ方法を選びます。
| キャプチャ方法 | 意味 |
|---|---|
&T(不変参照) | 値を読むだけで変更しない |
&mut T(可変参照) | 値を変更する |
T(ムーブ) | 所有権ごと受け取る |
明示的に所有権を移したい場合は move キーワードを使います。
fn main() {
let name = String::from("Rustacean");
// move で所有権をクロージャに移す
let greet = move || {
println!("Hello, {}!", name);
};
greet(); // Hello, Rustacean!
// println!("{}", name); // ← コンパイルエラー: name はムーブ済み
}
move は、スレッドにクロージャを渡すときなどによく使われます。
Iteratorとクロージャを組み合わせる
RustのIteratorトレイトには、クロージャを受け取るメソッドが多数用意されています。これらを組み合わせることで、ループを書かずに宣言的なデータ処理が実現できます。
map:各要素を変換する
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// 各要素を2倍にする
let doubled: Vec<i32> = numbers.iter()
.map(|&x| x * 2)
.collect();
println!("{:?}", doubled); // [2, 4, 6, 8, 10]
}
filter:条件に合う要素だけ残す
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
// 偶数だけ取り出す
let evens: Vec<&i32> = numbers.iter()
.filter(|&&x| x % 2 == 0)
.collect();
println!("{:?}", evens); // [2, 4, 6]
}
map と filter を連鎖させる
メソッドチェーンで複数の変換を組み合わせられます。
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8];
// 偶数だけ取り出して3倍にする
let result: Vec<i32> = numbers.iter()
.filter(|&&x| x % 2 == 0)
.map(|&x| x * 3)
.collect();
println!("{:?}", result); // [6, 12, 18, 24]
}
よく使うIteratorアダプタ
fold:要素を畳み込んで1つの値にまとめる
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// 合計を求める(初期値0から始めて足し合わせる)
let sum = numbers.iter().fold(0, |acc, &x| acc + x);
println!("合計: {}", sum); // 合計: 15
}
enumerate:インデックスと要素をセットで取得する
fn main() {
let fruits = vec!["apple", "banana", "cherry"];
fruits.iter().enumerate().for_each(|(i, fruit)| {
println!("{}: {}", i, fruit);
});
// 0: apple
// 1: banana
// 2: cherry
}
flat_map:変換 + 平坦化
fn main() {
let words = vec!["hello world", "foo bar"];
// 各文字列をスペースで分割して1つのイテレータにまとめる
let tokens: Vec<&str> = words.iter()
.flat_map(|s| s.split_whitespace())
.collect();
println!("{:?}", tokens); // ["hello", "world", "foo", "bar"]
}
実践例:構造体のVecを処理する
実際の開発ではstructのVecを扱うことが多いです。
#[derive(Debug)]
struct Product {
name: String,
price: u32,
in_stock: bool,
}
fn main() {
let products = vec![
Product { name: "キーボード".to_string(), price: 8000, in_stock: true },
Product { name: "マウス".to_string(), price: 3000, in_stock: false },
Product { name: "モニター".to_string(), price: 40000, in_stock: true },
Product { name: "ヘッドセット".to_string(), price: 12000, in_stock: true },
];
// 在庫あり かつ 価格が10000円以上の商品名を取得
let result: Vec<&str> = products.iter()
.filter(|p| p.in_stock && p.price >= 10_000)
.map(|p| p.name.as_str())
.collect();
println!("{:?}", result); // ["モニター", "ヘッドセット"]
// 在庫あり商品の合計金額
let total: u32 = products.iter()
.filter(|p| p.in_stock)
.map(|p| p.price)
.sum();
println!("在庫あり合計: {}円", total); // 在庫あり合計: 60000円
}
まとめ
- クロージャは
|引数| 処理の形で定義し、周囲の変数をキャプチャできる - 所有権を明示的に移したいときは
moveを使う Iteratorのアダプタ(map/filter/fold/flat_mapなど)とクロージャを組み合わせると、ループを書かずに簡潔なデータ処理ができる- 最後に
.collect()を呼ぶことでVecなどのコレクションに変換できる
Rustのイテレータは遅延評価(lazy evaluation)なので、チェーンを組んでも中間コレクションは生成されません。パフォーマンスを損なわずに読みやすいコードが書けるのは大きな魅力です。ぜひ積極的に活用してみてください!