第9章: 関数とクロージャ

学習目標

  • 関数の定義と呼び出しを理解する
  • クロージャの構文と使い方を学ぶ
  • 環境のキャプチャ方法を把握する
  • イテレータとクロージャの組み合わせを習得する

---

9.1 関数の基本

9.1.1 関数の定義

fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    let result = add(5, 3);
    println!("結果: {}", result);
}

構文

fn 関数名(引数名: 型, ...) -> 戻り値の型 {
    処理
}

9.1.2 戻り値

return キーワード

fn is_even(n: i32) -> bool {
    if n % 2 == 0 {
        return true;
    }
    false
}

式としての戻り値(推奨):

fn is_even(n: i32) -> bool {
    n % 2 == 0
}

戻り値なし

fn greet(name: &str) {
    println!("Hello, {}!", name);
}

// 明示的なunit型
fn greet_explicit(name: &str) -> () {
    println!("Hello, {}!", name);
}

9.1.3 複数の戻り値

タプルを使用:

fn divide(a: i32, b: i32) -> (i32, i32) {
    (a / b, a % b)
}

fn main() {
    let (quotient, remainder) = divide(10, 3);
    println!("{} ÷ {} = {} 余り {}", 10, 3, quotient, remainder);
}

Result型を使用:

fn divide_safe(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("ゼロ除算エラー"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    match divide_safe(10, 0) {
        Ok(result) => println!("結果: {}", result),
        Err(e) => println!("エラー: {}", e),
    }
}

---

9.2 関数のパラメータ

9.2.1 値渡し

fn change_value(mut x: i32) {
    x = 10;
    println!("関数内: {}", x); // 10
}

fn main() {
    let x = 5;
    change_value(x);
    println!("関数外: {}", x); // 5(変更されない)
}

9.2.2 参照渡し

不変参照

fn print_length(s: &String) {
    println!("長さ: {}", s.len());
}

fn main() {
    let text = String::from("hello");
    print_length(&text);
    println!("{}", text); // textはまだ使える
}

可変参照

fn append_world(s: &mut String) {
    s.push_str(", world");
}

fn main() {
    let mut text = String::from("hello");
    append_world(&mut text);
    println!("{}", text); // "hello, world"
}

9.2.3 所有権の移動

fn take_ownership(s: String) {
    println!("{}", s);
} // sがここでドロップ

fn main() {
    let text = String::from("hello");
    take_ownership(text);
    // println!("{}", text); // ❌ エラー!textはムーブ済み
}

---

9.3 クロージャの基本

9.3.1 クロージャとは

無名関数とも呼ばれ、環境をキャプチャできます:

fn main() {
    let add = |a, b| a + b;

    let result = add(5, 3);
    println!("結果: {}", result); // 8
}

構文の比較

// 関数
fn  add_fn(a: i32, b: i32) -> i32 { a + b }

// クロージャ(型注釈あり)
let add_closure = |a: i32, b: i32| -> i32 { a + b };

// クロージャ(型推論)
let add_closure = |a, b| a + b;

// クロージャ(複数行)
let add_closure = |a, b| {
    let result = a + b;
    result
};

9.3.2 環境のキャプチャ

クロージャは外側のスコープの変数をキャプチャできます:

fn main() {
    let x = 10;

    let print_x = || println!("x = {}", x);

    print_x(); // x = 10
}

関数との違い

fn main() {
    let x = 10;

    // ❌ 関数は環境をキャプチャできない
    // fn print_x() {
    //     println!("x = {}", x);
    // }

    // ✅ クロージャはキャプチャできる
    let print_x = || println!("x = {}", x);
    print_x();
}

9.3.3 キャプチャの3つの方法

1. 不変借用(&T)

fn main() {
    let list = vec![1, 2, 3];

    let print_list = || println!("{:?}", list);

    print_list();
    println!("{:?}", list); // ✅ listはまだ使える
}

2. 可変借用(&mut T)

fn main() {
    let mut list = vec![1, 2, 3];

    let mut add_item = || list.push(4);

    add_item();
    // println!("{:?}", list); // ❌ エラー(可変借用中)
}

3. 所有権の移動(move)

fn main() {
    let list = vec![1, 2, 3];

    let print_list = move || println!("{:?}", list);

    print_list();
    // println!("{:?}", list); // ❌ エラー!listはムーブ済み
}

moveキーワード

use std::thread;

fn main() {
    let list = vec![1, 2, 3];

    // スレッドで使用するには所有権を移動
    thread::spawn(move || {
        println!("{:?}", list);
    }).join().unwrap();

    // println!("{:?}", list); // ❌ ムーブ済み
}

---

9.4 クロージャの型

9.4.1 Fn トレイト

3つのトレイトが自動実装されます:

┌─────────────────────────────────────────────────────────┐
│          クロージャのトレイト                            │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  FnOnce                                                 │
│  - 環境を消費する                                       │
│  - 1回しか呼べない                                      │
│                                                         │
│  FnMut                                                  │
│  - 環境を可変借用                                       │
│  - 複数回呼べる                                         │
│  - 環境を変更できる                                     │
│                                                         │
│  Fn                                                     │
│  - 環境を不変借用                                       │
│  - 複数回呼べる                                         │
│  - 環境を変更しない                                     │
│                                                         │
│  継承関係: Fn ⊂ FnMut ⊂ FnOnce                         │
│                                                         │
└─────────────────────────────────────────────────────────┘

fn apply<F>(f: F) where F: Fn(i32) -> i32 {
    let result = f(5);
    println!("結果: {}", result);
}

fn main() {
    let double = |x| x * 2;
    apply(double);
}

9.4.2 FnOnce

fn consume<F>(f: F) where F: FnOnce() {
    f();
    // f(); // ❌ エラー!FnOnceは1回しか呼べない
}

fn main() {
    let s = String::from("hello");

    let consume_string = || {
        let _moved = s; // 所有権を移動
    };

    consume(consume_string);
}

9.4.3 FnMut

fn call_twice<F>(mut f: F) where F: FnMut() {
    f();
    f();
}

fn main() {
    let mut count = 0;

    let mut increment = || {
        count += 1;
        println!("count = {}", count);
    };

    call_twice(increment);
}

---

9.5 イテレータとクロージャ

9.5.1 map

各要素を変換:

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

    let doubled: Vec<i32> = numbers
        .iter()
        .map(|x| x * 2)
        .collect();

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

9.5.2 filter

条件に合う要素のみ抽出:

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

    let evens: Vec<i32> = numbers
        .iter()
        .filter(|&x| x % 2 == 0)
        .copied()
        .collect();

    println!("{:?}", evens); // [2, 4, 6]
}

9.5.3 fold

累積計算:

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

    let sum = numbers
        .iter()
        .fold(0, |acc, &x| acc + x);

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

9.5.4 チェーンの組み合わせ

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

    let result: i32 = numbers
        .iter()
        .filter(|&x| x % 2 == 0)  // 偶数のみ
        .map(|x| x * x)           // 二乗
        .sum();                   // 合計

    println!("結果: {}", result); // 220
}

---

9.6 高階関数

9.6.1 関数を引数に取る

fn apply_twice<F>(f: F, x: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    f(f(x))
}

fn main() {
    let double = |x| x * 2;

    let result = apply_twice(double, 5);
    println!("結果: {}", result); // 20
}

9.6.2 関数を返す

fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x + n
}

fn main() {
    let add_5 = make_adder(5);
    let add_10 = make_adder(10);

    println!("{}", add_5(3));  // 8
    println!("{}", add_10(3)); // 13
}

9.6.3 カリー化

fn curry_add(a: i32) -> impl Fn(i32) -> i32 {
    move |b| a + b
}

fn main() {
    let add_5 = curry_add(5);

    println!("{}", add_5(10)); // 15
    println!("{}", add_5(20)); // 25
}

---

9.7 実践例

例1:コレクション処理

#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
}

fn main() {
    let people = vec![
        Person { name: "Alice".to_string(), age: 30 },
        Person { name: "Bob".to_string(), age: 25 },
        Person { name: "Charlie".to_string(), age: 35 },
    ];

    // 30歳以上の人の名前を取得
    let names: Vec<&str> = people
        .iter()
        .filter(|p| p.age >= 30)
        .map(|p| p.name.as_str())
        .collect();

    println!("{:?}", names); // ["Alice", "Charlie"]
}

例2:エラーハンドリング

fn parse_numbers(input: &str) -> Result<Vec<i32>, std::num::ParseIntError> {
    input
        .split_whitespace()
        .map(|s| s.parse::<i32>())
        .collect()
}

fn main() {
    match parse_numbers("1 2 3 4 5") {
        Ok(numbers) => println!("{:?}", numbers),
        Err(e) => println!("エラー: {}", e),
    }
}

例3:設定オブジェクト

struct Config {
    threshold: i32,
}

impl Config {
    fn new(threshold: i32) -> Self {
        Config { threshold }
    }

    fn filter_below(&self) -> impl Fn(&i32) -> bool + '_ {
        move |&x| x >= self.threshold
    }
}

fn main() {
    let config = Config::new(5);
    let numbers = vec![1, 3, 5, 7, 9];

    let filtered: Vec<i32> = numbers
        .iter()
        .filter(config.filter_below())
        .copied()
        .collect();

    println!("{:?}", filtered); // [5, 7, 9]
}

例4:遅延評価

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

    // イテレータは遅延評価
    let iter = numbers
        .iter()
        .map(|x| {
            println!("処理中: {}", x);
            x * 2
        });

    println!("イテレータ作成完了");

    // collectで初めて実行される
    let result: Vec<i32> = iter.collect();
    println!("{:?}", result);
}

---

9.8 パフォーマンスの考慮

9.8.1 ゼロコスト抽象化

クロージャとイテレータはゼロコスト抽象化:

// ループ版
let mut sum = 0;
for i in 0..10 {
    sum += i;
}

// イテレータ版(同じ速さ)
let sum: i32 = (0..10).sum();

コンパイル後、両方とも同じ機械語に:

┌─────────────────────────────────────────────────────────┐
│         ゼロコスト抽象化の例                             │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  高レベル(Rustコード)                                 │
│  numbers.iter().map(|x| x * 2).sum()                   │
│                                                         │
│  ↓ コンパイル                                           │
│                                                         │
│  低レベル(機械語)                                     │
│  最適化されたループ                                     │
│  - インライン化                                         │
│  - ベクトル化                                           │
│  - オーバーヘッドなし                                   │
│                                                         │
└─────────────────────────────────────────────────────────┘

9.8.2 イテレータ vs ループ

イテレータの利点

// ✅ 読みやすい
let sum: i32 = numbers.iter().sum();

// ✅ 安全(境界チェック不要)
let evens: Vec<i32> = numbers
    .iter()
    .filter(|&x| x % 2 == 0)
    .copied()
    .collect();

// ✅ 並列化が容易(rayon クレート)
use rayon::prelude::*;
let sum: i32 = numbers.par_iter().sum();

---

9.9 関数ポインタ

9.9.1 fn型

fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn do_twice(f: fn(i32, i32) -> i32, arg1: i32, arg2: i32) -> i32 {
    f(arg1, arg2) + f(arg1, arg2)
}

fn main() {
    let result = do_twice(add, 5, 3);
    println!("{}", result); // 16
}

fnとFnの違い

fn  → 関数ポインタ(環境なし)
Fn  → クロージャトレイト(環境あり)

---

9.10 まとめ

学んだこと

  • 関数の基本
- 定義と呼び出し - パラメータと戻り値 - 所有権と借用

  • クロージャ
- 無名関数 - 環境のキャプチャ - Fn、FnMut、FnOnce

  • イテレータ
- map、filter、fold - メソッドチェーン - 遅延評価

  • 高階関数
- 関数を引数に取る - 関数を返す

次のステップ

次の章では、構造体を学びます: