第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など、メモリ管理の高度な技術を理解します。

    ---

    参考資料

  • The Rust Book: Closures
  • Rust by Example: Closures
  • Fn/FnMut/FnOnce traits