404 Not Found

404 Not Found


nginx

Rustのクロージャ:環境をキャプチャする匿名関数

クロージャとは、周囲の環境から変数をキャプチャできる匿名関数です。関数と同じように呼び出されますが、定義されたスコープ内の変数を「記憶」することができます。

正規関数を「テイクアウトメニューのセットメニュー」(材料はパラメータから得られる)に例えるなら、クロージャは「自分の冷蔵庫にある食材」(材料は環境から得られる)に例えることができます。クロージャは、定義された時点で存在していた変数にアクセスすることができます。これは、まるで冷蔵庫から食材を取り出して、すぐに料理を始めるようなものです。


1. 学習内容


2. 概念図

以下のMermaid図は、3種類のクロージャキャプチャ(Fn、FnMut、FnOnce)間の関係と相違点を示しています:

100%
graph TB
    A["Closure Closure"] --> B["Fn<br/>Immutable Borrowing &T"]
    A --> C["FnMut<br/>Variable Borrowing &mut T"]
    A --> D["FnOnce<br/>Consumer Ownership T"]

    B --> E["let c = || println!(&#34;{ }&#34;, x);"]
    B --> F["Can be called multiple times"]
    B --> G["Environment variables are read-only and immutable"]

    C --> H["let mut c = || x += 1;"]
    C --> I["Can be called multiple times"]
    C --> J["Environment variables can be modified"]

    D --> K["let c = || drop(x);"]
    D --> L["Can only be called once"]
    D --> M["Ownership has been transferred to the closure"]

    B -.-> |Automatic Inference| N["The compiler, based on the closure body<br/>Select the minimum requirement trait"]
    C -.-> N
    D -.-> N
    N --> O["move Mandatory Keywords FnOnce"]

3. フードデリバリーの評価システムの背景

(1) 面倒な点:採点ルールごとに個別の関数を作成しなければならないこと

Mia(ミア)は、フードデリバリーの評価システムを開発しています。ユーザーはレストランを評価できますが、評価のルールはさまざまです:

最初は、彼女はルールごとに関数を記述しました:

RUST
struct Review {
    store: String,
    rating: u32,
}

fn normal_score(r: &Review) -> u32 {
    r.rating
}

fn blogger_score(r: &Review) -> u32 {
    r.rating * 2
}

fn inspector_score(r: &Review) -> u32 {
    if r.rating < 3 { 0 } else { r.rating }
}

fn main() {
    let reviews = vec![
        Review { store: "Pizzaplace".into(), rating: 4 },
        Review { store: "BurgerKing".into(), rating: 2 },
        Review { store: "SushiBar".into(), rating: 5 },
    ];

    for r in &reviews {
        println!("{}: normal={}, blogger={}, inspector={}",
            r.store, normal_score(r), blogger_score(r), inspector_score(r));
    }
}

コアロジックの違いはさておき、これら3つの関数の関数シグネチャはまったく同じです。もしプロダクトマネージャーが「スーパーメンバー向けのスコアリングルールをもう1つ追加してほしい」と言った場合、別の関数を記述する必要があります。さらに、これらのスコアリングルールは「動的に生成」することができません。例えば、「スコア × 係数」における係数は、関数内にハードコーディングされなければなりません。

(2) より複雑な要件:動的に変化する重み付け係数

プロダクトマネージャーは次のように述べた。「評価係数は(プロモーション活動に応じて)日々変化し、ユーザーはさまざまな評価戦略(スコアリング、重み付け、低評価へのペナルティ)を選択できる。これは標準関数では実現できない。関数のシグネチャはコンパイル時に固定されており、外部からの動的な係数を考慮に入れることができないからだ。」

RUST
// I want to use a function,However, the coefficient is dynamic.
let weight = 1.5;  // Today's Weighting Coefficient
// fn weighted_score(r: &Review) -> u32 {
//     (r.rating as f64 * weight) as u32  // ❌ Compilation Error:weight Outside the function's scope
// }

(3) Rustのクロージャに関する解決策

RUST
struct Review {
    store: String,
    rating: u32,
}

fn main() {
    let reviews = vec![
        Review { store: "Pizzaplace".into(), rating: 4 },
        Review { store: "BurgerKing".into(), rating: 2 },
        Review { store: "SushiBar".into(), rating: 5 },
    ];

    // Closure 1: normal score (no capture)
    let normal = |r: &Review| r.rating;

    // Closure 2: weighted score (captures `weight` from environment)
    let weight: f64 = 1.5;
    let weighted = |r: &Review| (r.rating as f64 * weight) as u32;

    // Closure 3: inspector (captures `threshold` from environment)
    let threshold = 3;
    let inspector = |r: &Review| if r.rating < threshold { 0 } else { r.rating };

    for r in &reviews {
        println!("{}: normal={}, weighted={}, inspector={}",
            r.store, normal(r), weighted(r), inspector(r));
    }
}

出力:

TEXT
Pizzaplace: normal=4, weighted=6, inspector=4
BurgerKing: normal=2, weighted=3, inspector=0
SushiBar: normal=5, weighted=7, inspector=5

クロージャ |r| r.rating * weight は外部変数 weight をキャプチャしますが、これは通常の関数では不可能なことです。weightthreshold は動的に変化する可能性があり(たとえば、ユーザーの入力や設定ファイルに基づいて)、クロージャはそれらの現在の値を自動的にキャプチャします。これがクロージャの核心的な価値です:周囲の変数を「盗み出し」、それらを利用できる関数


4. 基本概念

(1) 閉鎖システムの概要

100%
graph TB
    A[Rust Closures] --> B[Syntax]
    A --> C[Capture Modes]
    A --> D[move Keyword]
    A --> E[As Parameters]
    A --> F[With Iterators]

    B --> B1["|param1, param2| expr"]
    B --> B2["|param| { multiple; statements; }"]

    C --> C1["Fn: immutable borrow (&T)"]
    C --> C2["FnMut: mutable borrow (&mut T)"]
    C --> C3["FnOnce: ownership (T)"]

    D --> D1["let c = move || x;"]
    D --> D2["Forces ownership transfer"]

    E --> E1["fn pointer: fn(T) -> U"]
    E --> E2["Fn trait: impl Fn(T) -> U"]
    E --> E3["FnMut / FnOnce trait bounds"]

    F --> F1["iter().map(|x| x + 1)"]
    F --> F2["iter().filter(|x| x > 0)"]
    F --> F3["Chained: map().filter().collect()"]

(2) 3つのキャプチャモードの比較

特性 取得方法 呼び出し回数 環境を変更可能か? 複数回呼び出し可能か?
FnOnce 所有権を消費する(移動) 1回 環境変数を消費できる いいえ(所有権はすでに譲渡済み)
FnMut 変数の借用 複数回 環境変数を変更可能 はい
Fn 不変の借用 複数回 環境変数は変更できない はい

(3) 関数ポインタ fn 対 クロージャ Fn

特集 関数ポインタ fn クロージャ Fn トレイト
環境変数を取得しますか? いいえ—パラメータのみ使用可能です はい—環境変数を取得できます
パラメータとして使用できますか? はい はい(より一般的な場合)
クロージャーは扱えますか? いいえ はい
タイプサイズ fn(T) -> U (ポインタサイズ) クロージャごとにサイズが異なる
パフォーマンス 判定済み コンパイラによるインライン化可能
構文 fn foo(x: i32) -> i32 |x| x + 1

(4) クロージャーの取得手法の比較

取得方法 構文例 所有権に関する考慮事項 クロージャの呼び出し可能回数 適用可能なシナリオ
不変の借用 |x| x + var 借用 &T 複数 環境変数への読み取り専用アクセス
可変借用 |x| { var += 1; ... } 借用 &mut T 複数(排他的) 環境変数を変更する必要がある
所有権の移動 move |x| x + var T を取得 1回限り(消費された場合) クロージャは参照よりも長く存続しなければならない
キャプチャなし |x| x + 1 なし 複数 関数ポインタに相当

5. クロージャの例

(1) ▶ サンプル:クロージャーの基本構文 — フードデリバリーの評価 (難易度 ⭐)

RUST
// ============================================
// Closure basics: syntax, type inference, calling
// ============================================

struct Review {
    store: String,
    rating: u32,
}

fn main() {
    let reviews = vec![
        Review { store: String::from("Pizzaplace"), rating: 4 },
        Review { store: String::from("BurgerKing"), rating: 2 },
        Review { store: String::from("SushiBar"), rating: 5 },
        Review { store: String::from("NoodleHouse"), rating: 3 },
    ];

    // --- Syntax variation 1: single expression ---
    let double = |r: &Review| r.rating * 2;
    // --- Syntax variation 2: block body ---
    let triple = |r: &Review| {
        let result = r.rating * 3;
        println!("    (triple: {} -> {})", r.rating, result);
        result
    };
    // --- Syntax variation 3: type inference ---
    // Rust infers parameter and return types from usage
    let half = |r| r.rating / 2;  // type inferred as &Review -> u32

    println!("--- Rating Calculations ---");
    for r in &reviews {
        println!("{} (rating: {}):", r.store, r.rating);
        println!("  double={}, half={}", double(r), half(r));
        let _ = triple(r);
    }

    // --- Closures that capture variables ---
    let min_rating = 3;
    let min_rating2 = 4;

    // Capture `min_rating` from the surrounding scope
    let is_pass = |r: &Review| r.rating >= min_rating;
    let is_good = |r: &Review| r.rating >= min_rating2;

    println!("\n--- Pass Check (min: {}) ---", min_rating);
    for r in &reviews {
        println!("{}: {}", r.store, if is_pass(r) { "PASS" } else { "FAIL" });
    }

    println!("\n--- Good Check (min: {}) ---", min_rating2);
    for r in &reviews {
        println!("{}: {}", r.store, if is_good(r) { "GOOD" } else { "OK" });
    }

    // --- Using closures as function arguments ---
    fn check_reviews(reviews: &[Review], label: &str, scorer: impl Fn(&Review) -> u32) {
        print!("{}: ", label);
        for r in reviews {
            print!("{}->{} ", r.store, scorer(r));
        }
        println!();
    }

    let weight = 2;
    let weighted_scorer = |r: &Review| r.rating * weight;

    check_reviews(&reviews, "Weighted(×2)", weighted_scorer);
    check_reviews(&reviews, "Normal", |r| r.rating);
    check_reviews(&reviews, "Bonus", |r| if r.rating >= 4 { r.rating + 1 } else { r.rating });
}

出力:

TEXT
--- Rating Calculations ---
Pizzaplace (rating: 4):
  double=8, half=2
    (triple: 4 -> 12)
BurgerKing (rating: 2):
  double=4, half=1
    (triple: 2 -> 6)
SushiBar (rating: 5):
  double=10, half=2
    (triple: 5 -> 15)
NoodleHouse (rating: 3):
  double=6, half=1
    (triple: 3 -> 9)

--- Pass Check (min: 3) ---
Pizzaplace: PASS
BurgerKing: FAIL
SushiBar: PASS
NoodleHouse: PASS

--- Good Check (min: 4) ---
Pizzaplace: GOOD
BurgerKing: OK
SushiBar: GOOD
NoodleHouse: OK

--- Weighted(×2) ---
Pizzaplace->8 BurgerKing->4 SushiBar->10 NoodleHouse->6
Normal: Pizzaplace->4 BurgerKing->2 SushiBar->5 NoodleHouse->3
Bonus: Pizzaplace->5 BurgerKing->2 SushiBar->6 NoodleHouse->3

クロージャの構文には、単一式 |x| expr、ブロック |x| { stmt; expr }、および型推論 |x| x+1(文脈から型が推論される)の3つのスタイルがあります。環境変数のキャプチャはクロージャー独自の機能です。is_passmin_ratingをキャプチャし、weighted_scorerweightをキャプチャします。check_reviews関数はimpl Fn(&Review) -> u32を受け入れます。つまり、Fnトレイトを実装する任意のクロージャーをこの関数に渡すことができます。


(2) ▶ サンプル:キャプチャモード — Fn / FnMut / FnOnce (難易度 ⭐⭐)

RUST
// ============================================
// Three capture modes: Fn, FnMut, FnOnce
// ============================================

fn main() {
    println!("=== Fn: Immutable Borrow ===");
    let scores = vec![10, 20, 30];
    // Fn closure: only reads captured variables (immutable borrow)
    let print_scores = || {
        println!("Scores: {:?}", scores);  // &Vec<i32>
    };
    print_scores();  // Can be called multiple times
    print_scores();
    println!("Still accessible: {:?}", scores);  // scores not moved

    println!("\n=== FnMut: Mutable Borrow ===");
    let mut counters = vec![0, 0, 0];
    // FnMut closure: can mutate captured variables
    let mut increment = || {
        for c in &mut counters {
            *c += 1;
        }
    };
    increment();  // Can be called multiple times
    increment();
    increment();
    println!("Counters: {:?}", counters);  // [3, 3, 3]

    println!("\n=== FnOnce: Ownership Consumed ===");
    let name = String::from("Pizzaplace");
    // FnOnce closure: consumes the captured variable
    let consume = || {
        println!("Consuming: {}", name);
        drop(name);  // Explicitly drop (consumes ownership)
    };
    consume();
    // consume();  // ❌ Compile error: closure can only be called once
    // println!("{}", name);  // ❌ Compile error: name was moved

    println!("\n=== FnOnce with move ===");
    let data = vec![1, 2, 3, 4, 5];
    // `move` forces the closure to take ownership of `data`
    let compute = move || {
        let sum: i32 = data.iter().sum();
        println!("Sum of data: {}", sum);
        // data is dropped here at end of closure
    };
    compute();
    // println!("{:?}", data);  // ❌ Compile error: data was moved into the closure
    // compute();  // ❌ Could also fail if the closure consumed data

    println!("\n=== Practical: Closure Type Selection ===");
    let items = vec!["apple", "banana", "cherry"];

    // Fn: read-only
    let count = || items.len();
    println!("Count (Fn): {}", count());

    // FnMut: modify captured variable
    let mut store = String::new();
    let mut append = |item: &str| {
        if !store.is_empty() { store.push_str(", "); }
        store.push_str(item);
    };
    for item in &items {
        append(item);
    }
    println!("Store (FnMut modified): {}", store);
    let store_len = store.len();
    println!("Store length: {}", store_len);
}

出力:

TEXT
=== Fn: Immutable Borrow ===
Scores: [10, 20, 30]
Scores: [10, 20, 30]
Still accessible: [10, 20, 30]

=== FnMut: Mutable Borrow ===
Counters: [3, 3, 3]

=== FnOnce: Ownership Consumed ===
Consuming: Pizzaplace

=== FnOnce with move ===
Sum of data: 15

=== Practical: Closure Type Selection ===
Count (Fn): 3
Store (FnMut modified): apple, banana, cherry
Store length: 20

コンパイラは、クロージャの本体がキャプチャされた変数にどのようにアクセスするか based on に基づいて、使用するトレイトを自動的に推論します:読み取り専用アクセスFn変更可能なアクセスFnMut(この場合、クロージャは mut で宣言されている必要があります)、所有権の消費FnOnce(1回のみ呼び出し可能)。キーワード move は、所有権をクロージャへ強制的に譲渡します。これはマルチスレッドのシナリオで一般的に使用されます(thread::spawn には 'static ライフタイムが必要です)。


(3) ▶ サンプル:引数としてのクロージャー――関数ポインタと Fn トレイトの比較 (難易度 ⭐⭐)

RUST
// ============================================
// Closures as parameters: fn pointer vs Fn trait
// ============================================

// --- Version 1: Accepts ONLY function pointers ---
// fn(T) -> U is a function pointer type (no environment capture)
fn apply_fn_pointer(f: fn(i32) -> i32, x: i32) -> i32 {
    f(x)
}

// --- Version 2: Accepts ANY Fn trait (including closures) ---
// impl Fn(T) -> U accepts both fn pointers and closures
fn apply_fn<F>(f: F, x: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    f(x)
}

// --- Version 3: FnMut parameter ---
fn apply_twice<F>(mut f: F, x: i32) -> i32
where
    F: FnMut(i32) -> i32,
{
    f(f(x))
}

// --- Version 4: FnOnce parameter ---
fn apply_once<F>(f: F, x: i32) -> i32
where
    F: FnOnce(i32) -> i32,
{
    f(x)
}

// A regular function (can be used as fn pointer)
fn square(x: i32) -> i32 {
    x * x
}

fn main() {
    // --- Regular functions as fn pointers ---
    println!("--- Function Pointers ---");
    // `square` is a function, can be passed as fn(i32) -> i32
    println!("square(5) via fn ptr: {}", apply_fn_pointer(square, 5));

    // Annotated closure (no capture) can be coerced to fn pointer
    let triple = |x: i32| x * 3;
    println!("triple(5) via fn ptr: {}", apply_fn_pointer(triple, 5));

    // --- Closures with Fn trait ---
    println!("\n--- Fn Trait (generic) ---");
    let base = 10;
    // This closure captures `base`, so it CANNOT be a fn pointer
    let add_base = |x: i32| x + base;
    println!("add_base(5): {}", apply_fn(add_base, 5));

    // Both fn pointers and closures work with Fn trait
    println!("square(6) via Fn: {}", apply_fn(square, 6));

    // --- FnMut in action ---
    println!("\n--- FnMut ---");
    let mut accum = 0;
    let mut accumulate = |x: i32| {
        accum += x;
        accum
    };
    println!("accumulate(5): {}", apply_twice(&mut accumulate, 5));
    // After apply_twice: accum = 5 + 5 = 10, then 10 + 5 = 15
    println!("Final accum: {}", accum);

    // --- FnOnce in action ---
    println!("\n--- FnOnce ---");
    let owned = String::from("value: ");
    let describe = |x: i32| {
        println!("{} {}", owned, x);
        x
    };
    println!("describe(42): {}", apply_once(describe, 42));
    // describe was consumed (FnOnce), cannot call again

    // --- Practical: scoring system with different strategies ---
    println!("\n--- Practical: Scoring Strategies ---");

    fn run_strategy<F>(reviews: &[u32], label: &str, strategy: F)
    where
        F: Fn(u32) -> u32,
    {
        let scores: Vec<u32> = reviews.iter().map(|&r| strategy(r)).collect();
        println!("{}: {:?}", label, scores);
    }

    let ratings = [4, 2, 5, 3, 1];
    let bonus_threshold = 4;
    let bonus_points = 1;

    // Different scoring strategies, all as closures
    run_strategy(&ratings, "Normal", |r| r);
    run_strategy(&ratings, "Double", |r| r * 2);
    run_strategy(&ratings, "Bonus", |r| if r >= bonus_threshold { r + bonus_points } else { r });
    run_strategy(&ratings, "Penalty", |r| if r < 3 { 0 } else { r });
}

出力:

TEXT
--- Function Pointers ---
square(5) via fn ptr: 25
triple(5) via fn ptr: 15

--- Fn Trait (generic) ---
add_base(5): 15
square(6) via Fn: 36

--- FnMut ---
accumulate(5): 15
Final accum: 15

--- FnOnce ---
value:  42
describe(42): 42

--- Practical: Scoring Strategies ---
Normal: [4, 2, 5, 3, 1]
Double: [8, 4, 10, 6, 2]
Bonus: [5, 2, 6, 3, 1]
Penalty: [4, 0, 5, 3, 0]

関数ポインタ fn(i32) -> i32 は、通常の関数および環境をキャプチャしないクロージャのみを受け入れることができます。Fn トレイト(ジェネリック)は、あらゆるクロージャ(環境をキャプチャするものを含む)を受け入れることができます。選択ルール:FFI の場合や、固定の関数型を格納する必要がある場合は fn ポインタを使用し、環境をキャプチャするクロージャが必要なシナリオでは Fn/FnMut/FnOnce トレイトを受け入れる。run_strategy関数は、impl Fn(u32) -> u32(4種類の異なるスコアリング戦略)をクロージャとして受け入れます。これには、bonus_thresholdおよびbonus_pointsをキャプチャするボーナス戦略も含まれます。


(4) ▶ サンプル:クロージャとイテレータの組み合わせ — map / filter / collect (難易度 ⭐⭐⭐)

RUST
// ============================================
// Closures + Iterator combinators: map, filter, collect
// ============================================

#[derive(Debug)]
struct Order {
    store: String,
    items: Vec<String>,
    rating: u32,
}

fn main() {
    let orders = vec![
        Order {
            store: String::from("Pizzaplace"),
            items: vec!["Margherita".into(), "Cola".into()],
            rating: 4,
        },
        Order {
            store: String::from("BurgerKing"),
            items: vec!["Whopper".into(), "Fries".into(), "Shake".into()],
            rating: 2,
        },
        Order {
            store: String::from("SushiBar"),
            items: vec!["Salmon".into(), "Tuna".into()],
            rating: 5,
        },
        Order {
            store: String::from("NoodleHouse"),
            items: vec!["Ramen".into(), "Gyoza".into(), "Tea".into()],
            rating: 3,
        },
    ];

    // --- map: transform each element ---
    println!("--- map: store names uppercase ---");
    let store_names: Vec<String> = orders
        .iter()
        .map(|o| o.store.to_uppercase())
        .collect();
    println!("{:?}", store_names);

    // --- filter: keep elements matching a condition ---
    println!("\n--- filter: orders with rating >= 4 ---");
    let good_orders: Vec<&Order> = orders
        .iter()
        .filter(|o| o.rating >= 4)
        .collect();
    for o in &good_orders {
        println!("{:?}", o);
    }

    // --- filter + map: chained ---
    println!("\n--- filter + map: good store names ---");
    let good_stores: Vec<String> = orders
        .iter()
        .filter(|o| o.rating >= 4)
        .map(|o| format!("★ {} (rating: {})", o.store, o.rating))
        .collect();
    for s in &good_stores {
        println!("{}", s);
    }

    // --- map + filter: compute and refine ---
    println!("\n--- map + filter: average rating per item ---");
    let avg_ratings: Vec<String> = orders
        .iter()
        .map(|o| {
            // Compute average rating per item
            let item_count = o.items.len() as f64;
            let avg = o.rating as f64 / item_count;
            (o.store.clone(), avg, o.items.len())
        })
        .filter(|(_, avg, _)| *avg > 1.5)  // Only stores with high per-item rating
        .map(|(store, avg, count)| format!("{}: {:.2}/item ({} items)", store, avg, count))
        .collect();
    for s in &avg_ratings {
        println!("{}", s);
    }

    // --- Advanced: closures capturing external state ---
    println!("\n--- Advanced: dynamic threshold filtering ---");
    let min_rating = 3;
    let category = "Premium";

    let premium_stores: Vec<String> = orders
        .iter()
        .filter(|o| o.rating >= min_rating)
        .map(|o| format!("[{}] {} (rating: {})", category, o.store, o.rating))
        .collect();

    for s in &premium_stores {
        println!("{}", s);
    }

    // --- All items from high-rated stores ---
    println!("\n--- Flat items from high-rated stores ---");
    let threshold = 3;
    let recommended_items: Vec<&String> = orders
        .iter()
        .filter(|o| o.rating > threshold)
        .flat_map(|o| o.items.iter())
        .collect();
    println!("Recommended: {:?}", recommended_items);

    // --- Custom scoring with filter ---
    println!("\n--- Custom filter: weighted score ---");
    let weight = 1.2;
    let min_score = 4.0;

    let top_orders: Vec<&Order> = orders
        .iter()
        .filter(|o| (o.rating as f64 * weight) >= min_score)
        .collect();
    for o in &top_orders {
        let weighted = o.rating as f64 * weight;
        println!("{}: raw={}, weighted={:.1}, pass={}", o.store, o.rating, weighted, weighted >= min_score);
    }

    // --- count, any, all ---
    println!("\n--- Aggregate checks ---");
    let high_rated_count = orders.iter().filter(|o| o.rating >= 4).count();
    println!("Orders with rating >= 4: {}", high_rated_count);

    let has_bad_order = orders.iter().any(|o| o.rating <= 1);
    println!("Has any bad order (rating <= 1): {}", has_bad_order);

    let all_rated = orders.iter().all(|o| o.rating >= 1);
    println!("All orders rated at least 1: {}", all_rated);
}

出力:

TEXT
--- map: store names uppercase ---
["PIZZAPLACE", "BURGERKING", "SUSHIBAR", "NOODLEHOUSE"]

--- filter: orders with rating >= 4 ---
Order { store: "Pizzaplace", items: ["Margherita", "Cola"], rating: 4 }
Order { store: "SushiBar", items: ["Salmon", "Tuna"], rating: 5 }

--- filter + map: good store names ---
★ Pizzaplace (rating: 4)
★ SushiBar (rating: 5)

--- map + filter: average rating per item ---
Pizzaplace: 2.00/item (2 items)
SushiBar: 2.50/item (2 items)

--- Advanced: dynamic threshold filtering ---
[Premium] Pizzaplace (rating: 4)
[Premium] SushiBar (rating: 5)
[Premium] NoodleHouse (rating: 3)

--- Flat items from high-rated stores ---
Recommended: ["Margherita", "Cola", "Salmon", "Tuna"]

--- Custom filter: weighted score ---
Pizzaplace: raw=4, weighted=4.8, pass=true
SushiBar: raw=5, weighted=6.0, pass=true

--- Aggregate checks ---
Orders with rating >= 4: 2
Has any bad order (rating <= 1): false
All orders rated at least 1: true

イテレータコンビネータは、クロージャの最も強力な応用例の一つです: map(各要素の変換)、filter(条件に基づく要素の保持)、flat_map(ネストされたイテレータの平坦化)、any/all(集計チェック)、count(カウント)。ここでのクロージャの価値は、外部の動的値を捕捉する能力(min_ratingweightcategoryなど)にあり、これによりデータ処理ロジックと設定を分離できます。連鎖呼び出し iter().filter().map().collect() は、Rust において最も表現力豊かなイディオムの一つです。


(5) ▶ サンプル:総合演習 — クロージャを用いた設定駆動型データパイプラインの実装(難易度 ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example: Closure Capture + Higher-Order Functions + Iterator Chain
// ============================================

struct DataPipeline<'a, T> {
    data: Vec<T>,
    filters: Vec<Box<dyn Fn(&T) -> bool + 'a>>,
    transforms: Vec<Box<dyn Fn(T) -> T + 'a>>,
}

impl<'a, T: Clone + 'a> DataPipeline<'a, T> {
    fn new(data: Vec<T>) -> Self {
        DataPipeline { data, filters: Vec::new(), transforms: Vec::new() }
    }

    fn filter<F: Fn(&T) -> bool + 'a>(mut self, f: F) -> Self {
        self.filters.push(Box::new(f));
        self
    }

    fn map<F: Fn(T) -> T + 'a>(mut self, f: F) -> Self {
        self.transforms.push(Box::new(f));
        self
    }

    fn execute(self) -> Vec<T> {
        let mut result = self.data;
        for f in &self.filters {
            result.retain(f);
        }
        for t in &self.transforms {
            result = result.into_iter().map(t).collect();
        }
        result
    }
}

fn main() {
    let min_score = 60;
    let bonus = 10;
    let max_score = 100;

    let pipeline = DataPipeline::new(vec![85, 42, 95, 58, 73, 30, 88])
        .filter(move |&&x| x >= min_score)
        .filter(|&&x| x < max_score)
        .map(move |x| x + bonus);

    let result = pipeline.execute();
    println!("Filter+ After the bonus points are added: {:?}", result);

    let multiplier = 2;
    let adjusted: Vec<i32> = result.into_iter()
        .map(|x| x * multiplier)
        .filter(|x| *x < 200)
        .collect();
    println!("Double+ Upper Limit Filtering: {:?}", adjusted);

    let threshold = 150;
    let has_high = adjusted.iter().any(|&x| x > threshold);
    let all_above_100 = adjusted.iter().all(|&x| x > 100);
    println!("Has values greater than {}: {}, All > 100: {}", threshold, has_high, all_above_100);
}

出力:

TEXT
Filter+ After the bonus points are added: [95, 83, 98]
Double+ Upper Limit Filtering: [190, 166, 196]
Has values greater than 150: true, All > 100: true

DataPipelineBox<dyn Fn> を使用してクロージャを格納し、move は外部の設定値 (min_scorebonus) を取得し、filter().map() への連鎖呼び出しによって処理パイプラインが構築されます。クロージャにより、「設定とロジックを分離する」ことが可能になります。


❓ よくある質問

Q クロージャと通常の関数の根本的な違いは何ですか?
A クロージャは周囲のスコープにある変数をキャプチャできますが、通常の関数にはそれができません。
Q コンパイラは Fn、FnMut、FnOnce のうちどれを選択するのですか?
A コンパイラは、クロージャの本体がキャプチャされた変数にどのようにアクセスするかに基づいて自動的に推論します。
Q move キーワードはいつ使用すべきですか?
A クロージャがキャプチャされた変数を所有する必要がある場合、特にマルチスレッドのシナリオにおいてです。
Q 関数ポインタ fn(T) -> U とクロージャートレート Fn(T) -> U は互換性がありますか?
A 完全に互換性があるわけではありません。関数ポインタは環境をキャプチャできませんが、クロージャはキャプチャできます。
Q クロージャをパラメータとして使用する際、impl FnBox<dyn Fn>、それとも汎用的な F: Fn のどれを選ぶべきでしょうか?
A ジェネリック制約 F: Fn はパフォーマンスが最も優れており(静的ディスパッチ)、Box<dyn Fn> は最も柔軟性が高く(動的ディスパッチ)、impl Fn はジェネリックのシンタックスシュガーです。
Q クロージャーのパフォーマンスはどのようになっていますか?通常の関数よりも遅いのでしょうか?
A 環境をキャプチャしないクロージャーは、通常の関数とまったく同じパフォーマンスを示します。環境をキャプチャするクロージャーのオーバーヘッドはゼロ、あるいはほぼゼロです。

📖 まとめ


📝 練習問題

  1. 難易度 ⭐[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] を含む Vec<i32> があります。変数 threshold(値は 5)をキャプチャするクロージャを作成し、filter を使って threshold より大きい数値をフィルタリングし、map を使ってそれらを 2 倍にし、最後に collect を使って新しい Vec に追加し、結果を出力してください。

  2. 難易度 ⭐⭐: 各要素に変換関数を適用する関数 fn transform<F>(data: &[i32], f: F) -> Vec<i32> where F: Fn(i32) -> i32 を作成してください。関数 main 内で、3つのクロージャを作成してください:|x| x * 2(double型)、変数 add をキャプチャする加算クロージャ、および変数 max_val をキャプチャする切り捨てクロージャ(値が max_val を超える場合は max_val を返すもの)。それぞれについて transform を呼び出し、結果を出力してください。

  3. 難易度 ⭐⭐⭐: struct Product { name: String, price: f64, category: String } という名前の構造体があります。6つの商品インスタンスを作成し、それらをベクトルに格納してください。関数 fn analyze_products<F1, F2>(products: &[Product], category_filter: F1, price_adjuster: F2) where F1: Fn(&&Product) -> bool, F2: Fn(f64) -> f64 を記述し、まず filter を使用してカテゴリでフィルタリングし、次に map を使用して価格を調整し、最後に調整後の価格順に商品を並べ替えて、最も高価な商品の上位3つを出力するようにしてください。main 関数内で、異なるフィルタリングおよび価格調整の戦略を用いて、これを少なくとも2回テストしてください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%