第14章: クロージャ (Closures)
学習目標
- クロージャの基本構文と使い方を理解する
- Fn、FnMut、FnOnce トレイトの違いを理解する
- キャプチャの仕組み(値、参照、可変参照)を理解する
- 関数型プログラミングパターンを学ぶ
---
14.1 クロージャとは
14.1.1 基本概念
クロージャは環境をキャプチャできる匿名関数です。
┌─────────────────────────────────────────────────────────┐
│ 通常の関数 vs クロージャ │
├─────────────────────────────────────────────────────────┤
│ │
│ 【通常の関数】 │
│ - 名前を持つ │
│ - パラメータのみアクセス可能 │
│ - fn キーワードで定義 │
│ │
│ 【クロージャ】 │
│ - 匿名(変数に代入可能) │
│ - 環境の変数をキャプチャできる │
│ - |args| expression で定義 │
│ │
└─────────────────────────────────────────────────────────┘
14.1.2 基本構文
fn main() {
// 通常の関数
fn add_one(x: i32) -> i32 {
x + 1
}
// クロージャ(完全な型注釈)
let add_two = |x: i32| -> i32 { x + 2 };
// クロージャ(型推論)
let add_three = |x| x + 3;
// クロージャ(複数行)
let add_four = |x| {
let result = x + 4;
result
};
println!("{}", add_one(10)); // 11
println!("{}", add_two(10)); // 12
println!("{}", add_three(10)); // 13
println!("{}", add_four(10)); // 14
}
14.1.3 環境のキャプチャ
fn main() {
let x = 10;
// クロージャは外部の変数xをキャプチャできる
let add_x = |y| x + y;
println!("{}", add_x(5)); // 15
// 通常の関数ではできない!
// fn add_x_func(y: i32) -> i32 {
// x + y // エラー: `x` が見つからない
// }
}
---
14.2 クロージャのキャプチャ
14.2.1 3種類のキャプチャ方法
┌─────────────────────────────────────────────────────────┐
│ クロージャのキャプチャ方法 │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. 不変借用 (&T) │
│ └─ 読み取り専用でキャプチャ │
│ │
│ 2. 可変借用 (&mut T) │
│ └─ 変更可能でキャプチャ │
│ │
│ 3. 所有権の移動 (T) │
│ └─ 値そのものをキャプチャ │
│ │
│ Rustは必要最小限のキャプチャ方法を自動選択 │
│ │
└─────────────────────────────────────────────────────────┘
不変借用
fn main() {
let list = vec![1, 2, 3];
// 不変借用でキャプチャ
let only_borrows = || println!("From closure: {:?}", list);
println!("Before calling closure: {:?}", list);
only_borrows();
println!("After calling closure: {:?}", list);
// listはまだ使える
}
可変借用
fn main() {
let mut list = vec![1, 2, 3];
// 可変借用でキャプチャ
let mut borrows_mutably = || list.push(4);
// println!("Before: {:?}", list); // エラー!可変借用中
borrows_mutably();
println!("After: {:?}", list); // [1, 2, 3, 4]
}
所有権の移動
fn main() {
let list = vec![1, 2, 3];
// 所有権を移動
let consume = || {
println!("Consumed: {:?}", list);
drop(list); // 明示的にドロップ
};
consume();
// println!("{:?}", list); // エラー!所有権が移動済み
}
14.2.2 move キーワード
moveを使うと、強制的に所有権を移動してキャプチャします。
use std::thread;
fn main() {
let list = vec![1, 2, 3];
// moveがないとエラー(スレッドのライフタイムが不明)
thread::spawn(move || {
println!("From thread: {:?}", list);
}).join().unwrap();
// println!("{:?}", list); // エラー!所有権が移動済み
}
move が必要な理由:
fn main() {
let mut handles = vec![];
for i in 0..3 {
// moveがないとエラー!iの参照が無効になる可能性
handles.push(std::thread::spawn(move || {
println!("Thread {}", i);
}));
}
for handle in handles {
handle.join().unwrap();
}
}
---
14.3 Fn、FnMut、FnOnce トレイト
14.3.1 3つのトレイト
┌─────────────────────────────────────────────────────────┐
│ クロージャトレイトの階層 │
├─────────────────────────────────────────────────────────┤
│ │
│ FnOnce │
│ └─ 一度だけ呼べる(所有権を消費する可能性) │
│ │ │
│ │ │
│ FnMut : FnOnce │
│ └─ 複数回呼べる(環境を変更する) │
│ │ │
│ │ │
│ Fn : FnMut │
│ └─ 複数回呼べる(環境を変更しない) │
│ │
│ Fn が最も制約が強く、FnOnce が最も緩い │
│ │
└─────────────────────────────────────────────────────────┘
14.3.2 Fn - 不変クロージャ
fn call_twice<F>(f: F)
where
F: Fn(i32) -> i32,
{
println!("First: {}", f(1));
println!("Second: {}", f(2));
}
fn main() {
let add_ten = |x| x + 10;
call_twice(add_ten);
// First: 11
// Second: 12
}
14.3.3 FnMut - 可変クロージャ
fn call_multiple<F>(mut f: F)
where
F: FnMut(i32) -> i32,
{
println!("First: {}", f(1));
println!("Second: {}", f(2));
}
fn main() {
let mut count = 0;
let mut increment = |x| {
count += 1; // 環境を変更
x + count
};
call_multiple(&mut increment);
// First: 2 (1 + 1)
// Second: 4 (2 + 2)
println!("Final count: {}", count); // 2
}
14.3.4 FnOnce - 消費するクロージャ
fn call_once<F>(f: F)
where
F: FnOnce() -> String,
{
println!("{}", f());
}
fn main() {
let s = String::from("Hello");
let consume = || {
s // 所有権を返す
};
call_once(consume);
// call_once(consume); // エラー!2回目は呼べない
}
14.3.5 トレイト選択のガイドライン
// 例1: Fn - 環境を変更しない
fn apply_fn<F>(f: F, x: i32) -> i32
where
F: Fn(i32) -> i32,
{
f(x)
}
// 例2: FnMut - 環境を変更する
fn apply_fn_mut<F>(mut f: F, x: i32) -> i32
where
F: FnMut(i32) -> i32,
{
f(x)
}
// 例3: FnOnce - 所有権を消費する可能性
fn apply_fn_once<F>(f: F, x: i32) -> i32
where
F: FnOnce(i32) -> i32,
{
f(x)
}
fn main() {
let multiplier = 2;
let multiply = |x| x * multiplier;
println!("{}", apply_fn(multiply, 10)); // 20
let mut count = 0;
let mut increment = |x| {
count += 1;
x + count
};
println!("{}", apply_fn_mut(&mut increment, 10)); // 11
let s = String::from("value");
let consume = |x| {
println!("{}", s);
x
};
println!("{}", apply_fn_once(consume, 10)); // 10
}
---
14.4 イテレータとクロージャ
14.4.1 map / filter / fold の復習
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// map: 変換
let doubled: Vec<i32> = numbers.iter()
.map(|x| x * 2)
.collect();
println!("{:?}", doubled); // [2, 4, 6, 8, 10]
// filter: フィルタリング
let evens: Vec<&i32> = numbers.iter()
.filter(|x| *x % 2 == 0)
.collect();
println!("{:?}", evens); // [2, 4]
// fold: 畳み込み
let sum = numbers.iter()
.fold(0, |acc, x| acc + x);
println!("{}", sum); // 15
}
14.4.2 高度な例:データ処理
#[derive(Debug)]
struct Transaction {
amount: f64,
category: String,
}
fn main() {
let transactions = vec![
Transaction { amount: 100.0, category: "Food".to_string() },
Transaction { amount: 200.0, category: "Transport".to_string() },
Transaction { amount: 50.0, category: "Food".to_string() },
Transaction { amount: 300.0, category: "Entertainment".to_string() },
];
// Foodカテゴリの合計金額
let food_total = transactions.iter()
.filter(|t| t.category == "Food")
.map(|t| t.amount)
.sum::<f64>();
println!("Food total: {}", food_total); // 150.0
}
---
14.5 関数型プログラミングパターン
14.5.1 高階関数
// 関数を引数に取る関数
fn apply_operation<F>(x: i32, y: i32, op: F) -> i32
where
F: Fn(i32, i32) -> i32,
{
op(x, y)
}
fn main() {
let add = |x, y| x + y;
let multiply = |x, y| x * y;
println!("{}", apply_operation(5, 3, add)); // 8
println!("{}", apply_operation(5, 3, multiply)); // 15
}
14.5.2 関数を返す関数
fn create_multiplier(factor: i32) -> impl Fn(i32) -> i32 {
move |x| x * factor
}
fn main() {
let times_two = create_multiplier(2);
let times_ten = create_multiplier(10);
println!("{}", times_two(5)); // 10
println!("{}", times_ten(5)); // 50
}
14.5.3 カリー化(Currying)
fn curry<A, B, C, F>(f: F) -> impl Fn(A) -> Box<dyn Fn(B) -> C>
where
F: Fn(A, B) -> C + 'static,
A: 'static,
B: 'static,
C: 'static,
{
move |a| Box::new(move |b| f(a, b))
}
fn add(x: i32, y: i32) -> i32 {
x + y
}
fn main() {
let curried_add = curry(add);
let add_five = curried_add(5);
println!("{}", add_five(10)); // 15
}
14.5.4 コンビネータパターン
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// チェーン可能なコンビネータ
let result: i32 = numbers.iter()
.filter(|&x| x % 2 == 0) // 偶数のみ
.map(|x| x * x) // 二乗
.filter(|&x| x > 5) // 5より大きい
.sum(); // 合計
println!("{}", result); // 16 + 4 = 20
}
---
14.6 クロージャの実装詳細
14.6.1 クロージャは構造体
// これと同等のコードをコンパイラが生成
struct ClosureEnvironment {
captured_var: i32,
}
impl ClosureEnvironment {
fn call(&self, x: i32) -> i32 {
x + self.captured_var
}
}
fn main() {
let captured_var = 10;
let add_captured = |x| x + captured_var;
// 内部的には
let closure_env = ClosureEnvironment { captured_var: 10 };
println!("{}", closure_env.call(5)); // 15
}
14.6.2 型推論とクロージャ
fn main() {
// 最初の呼び出しで型が決まる
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
// let n = example_closure(5); // エラー!既にString型と推論済み
}
14.6.3 ジェネリクスとクロージャ
fn apply_to_3<F>(f: F) -> i32
where
F: Fn(i32) -> i32,
{
f(3)
}
fn main() {
let double = |x| x * 2;
let square = |x| x * x;
println!("{}", apply_to_3(double)); // 6
println!("{}", apply_to_3(square)); // 9
}
---
14.7 実践的なパターン
14.7.1 設定のビルダーパターン
struct Config {
host: String,
port: u16,
timeout: u64,
}
impl Config {
fn builder() -> ConfigBuilder {
ConfigBuilder::default()
}
}
struct ConfigBuilder {
host: Option<String>,
port: Option<u16>,
timeout: Option<u64>,
}
impl Default for ConfigBuilder {
fn default() -> Self {
ConfigBuilder {
host: None,
port: None,
timeout: None,
}
}
}
impl ConfigBuilder {
fn host<F>(mut self, f: F) -> Self
where
F: FnOnce() -> String,
{
self.host = Some(f());
self
}
fn port<F>(mut self, f: F) -> Self
where
F: FnOnce() -> u16,
{
self.port = Some(f());
self
}
fn build(self) -> Config {
Config {
host: self.host.unwrap_or_else(|| "localhost".to_string()),
port: self.port.unwrap_or(8080),
timeout: self.timeout.unwrap_or(30),
}
}
}
fn main() {
let config = Config::builder()
.host(|| "example.com".to_string())
.port(|| 3000)
.build();
println!("{}:{}", config.host, config.port);
}
14.7.2 遅延評価
struct Lazy<F>
where
F: FnOnce() -> i32,
{
init: Option<F>,
value: Option<i32>,
}
impl<F> Lazy<F>
where
F: FnOnce() -> i32,
{
fn new(init: F) -> Self {
Lazy {
init: Some(init),
value: None,
}
}
fn get(&mut self) -> i32 {
if let Some(value) = self.value {
value
} else {
let init = self.init.take().unwrap();
let value = init();
self.value = Some(value);
value
}
}
}
fn main() {
let mut lazy = Lazy::new(|| {
println!("Computing...");
42
});
println!("Lazy created");
println!("Value: {}", lazy.get()); // Computing... Value: 42
println!("Value: {}", lazy.get()); // Value: 42 (再計算しない)
}
14.7.3 コールバックパターン
struct Button {
label: String,
on_click: Option<Box<dyn Fn()>>,
}
impl Button {
fn new(label: &str) -> Self {
Button {
label: label.to_string(),
on_click: None,
}
}
fn on_click<F>(mut self, callback: F) -> Self
where
F: Fn() + 'static,
{
self.on_click = Some(Box::new(callback));
self
}
fn click(&self) {
println!("Button '{}' clicked", self.label);
if let Some(ref callback) = self.on_click {
callback();
}
}
}
fn main() {
let mut count = 0;
let button = Button::new("Click me")
.on_click(|| {
println!("Button was clicked!");
});
button.click();
button.click();
}
---
14.8 まとめ
クロージャの利点
┌─────────────────────────────────────────────────────────┐
│ クロージャを使うべき理由 │
├─────────────────────────────────────────────────────────┤
│ │
│ ✓ 簡潔な構文 │
│ └─ 小さな関数を簡潔に書ける │
│ │
│ ✓ 環境のキャプチャ │
│ └─ 周囲の変数にアクセス可能 │
│ │
│ ✓ ゼロコスト抽象化 │
│ └─ インライン化により高速 │
│ │
│ ✓ 関数型プログラミング │
│ └─ map, filter, fold等と組み合わせて強力 │
│ │
│ ✓ 柔軟性 │
│ └─ コールバック、イベントハンドラに最適 │
│ │
└─────────────────────────────────────────────────────────┘
次のステップ
次の章では、スマートポインタについて学びます。Box、Rc、Arcなど、メモリ管理の高度な技術を理解します。
---