Rust: Closures em Rust

Última atualização: 2026-08-26

Um closure é uma função anônima capaz de capturar variáveis do ambiente em que está inserida. Ele é chamado da mesma forma que uma função, mas pode “lembrar” das variáveis no escopo em que foi definido.

Se dissermos que uma função regular é como um “prato fixo em um cardápio de entrega” (onde os ingredientes vêm dos parâmetros), então um fechamento é como “os ingredientes da sua própria geladeira” (onde os ingredientes vêm do ambiente). Um fechamento pode acessar as variáveis que estavam presentes no momento em que foi definido — exatamente como pegar ingredientes da sua geladeira e cozinhar com eles na hora.


1. O que você vai aprender


2. Diagramas conceituais

O diagrama Mermaid a seguir ilustra as relações e diferenças entre os três tipos de captura de fechamento (Fn, FnMut e 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. A história por trás do sistema de avaliação de entrega de comida

(1) O incômodo: escrever uma função separada para cada regra de pontuação

A Mia (Mia) está desenvolvendo um sistema de avaliação para entrega de comida. Os usuários podem avaliar restaurantes, mas as regras de avaliação variam:

No início, ela escreveu uma função para cada regra:

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));
    }
}

Além das diferenças na lógica central, as três funções têm exatamente as mesmas assinaturas. Se o gerente de produto disser: “Adicione outra regra de pontuação para os Super Membros”, será necessário escrever outra função. Além disso, essas regras de pontuação não podem ser “geradas dinamicamente” — por exemplo, o coeficiente em “pontuação × coeficiente” deve ser codificado de forma estática na função.

(2) Requisitos mais complexos: fatores de ponderação que mudam dinamicamente

O gerente de produto afirmou: “Os coeficientes de classificação mudam diariamente (dependendo das atividades promocionais), e os usuários podem escolher diferentes estratégias de classificação (pontuação, ponderação, penalização de notas baixas). Isso não pode ser feito usando funções padrão — as assinaturas das funções são fixadas no momento da compilação e não podem levar em conta coeficientes dinâmicos externos.”

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) Soluções para closures em 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));
    }
}

Resultado:

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

Um closure |r| r.rating * weight captura variáveis externas weight — algo que as funções comuns não conseguem fazer. weight e threshold podem mudar dinamicamente (por exemplo, com base na entrada do usuário ou em arquivos de configuração), e o closure captura automaticamente seus valores atuais. Esse é o valor central dos closures: uma função que pode “roubar” variáveis do ambiente ao seu redor e usá-las.


4. Conceitos fundamentais

(1) Visão geral dos sistemas de fechamento

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) Comparação dos três modos de captura

Característica Método de captura Número de chamadas É possível modificar o ambiente? Pode ser chamado várias vezes?
FnOnce Consome propriedade (move) Uma vez Pode consumir variáveis de ambiente Não (a propriedade já foi transferida)
FnMut Uso de variáveis Várias vezes As variáveis de ambiente podem ser modificadas Sim
Fn Empréstimo imutável Várias vezes As variáveis de ambiente não podem ser modificadas Sim

(3) Ponteiros de função fn x Closures Fn

Recurso Ponteiros de função fn Closures Fn trait
Ele captura o ambiente? Não — só é possível usar parâmetros Sim — é possível capturar variáveis de ambiente
Pode ser usado como parâmetro? Sim Sim (de forma mais geral)
Aceita closures? Não Sim
Tamanho do tipo fn(T) -> U (tamanho do ponteiro) Fechamentos diferentes têm tamanhos diferentes
Desempenho Determinado Pode ser otimizado pelo compilador
Sintaxe fn foo(x: i32) -> i32 |x| x + 1

(4) Comparação dos métodos de captura de fechamento

Método de captura Exemplo de sintaxe Implicações de propriedade Número de vezes que o closure pode ser chamado Cenários aplicáveis
Empréstimo imutável |x| x + var Empréstimo &T Múltiplo Acesso somente leitura às variáveis de ambiente
Empréstimo variável |x| { var += 1; ... } Empréstimo &mut T Múltiplo (exclusivo) As variáveis de ambiente devem ser modificadas
Transferir propriedade move |x| x + var Obter T Apenas uma vez (se consumido) O closure deve permanecer ativo após a referência ser liberada
Sem captura |x| x + 1 Nenhuma Múltipla Equivalente a um ponteiro de função

5. Exemplos de fechamentos

▶ Exemplo 1: Sintaxe básica de closures — Avaliações de entrega de comida (Dificuldade ⭐)

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 });
}

Resultado:

TEXT 📖 Somente leitura
--- 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

Existem três estilos de sintaxe de closures: expressão única |x| expr, bloco |x| { stmt; expr } e inferência de tipo |x| x+1 (em que o tipo é inferido a partir do contexto). A captura de variáveis de ambiente é uma capacidade exclusiva dos closures: is_pass captura min_rating, e weighted_scorer captura weight. A função check_reviews aceita impl Fn(&Review) -> u32 — qualquer closure que implemente a característica Fn pode ser passado para ela.


▶ Exemplo 2: Modo de captura — Fn / FnMut / FnOnce (Dificuldade ⭐⭐)

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);
}

Resultado:

TEXT 📖 Somente leitura
=== 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

O compilador infere automaticamente qual trait usar com base na forma como o corpo do closure acessa as variáveis capturadas: acesso somente leituraFn, acesso mutávelFnMut (o closure deve ser declarado com mut), consumo de propriedadeFnOnce (só pode ser chamado uma vez). A palavra-chave move força a transferência de propriedade para o closure — comumente usada em cenários multithreading (thread::spawn requer o tempo de vida 'static).


▶ Exemplo 3: Closures como argumentos — Ponteiros de função x a característica Fn (Dificuldade ⭐⭐)

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 });
}

Resultado:

TEXT 📖 Somente leitura
--- 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]

Os ponteiros de função fn(i32) -> i32 só podem aceitar funções comuns e closures que não capturam o ambiente. A característica Fn (genérica) pode aceitar qualquer closure (incluindo aqueles que capturam o ambiente). Regras de seleção: Use ponteiros fn para FFI ou quando for necessário armazenar um tipo de função fixo; aceite traços Fn/FnMut/FnOnce para cenários que exijam closures que capturem o ambiente. As funções run_strategy aceitam impl Fn(u32) -> u32 — quatro estratégias de pontuação diferentes — como closures, incluindo estratégias de bônus que capturam bonus_threshold e bonus_points.


▶ Exemplo 4: Combinando closures e iteradores — map / filter / collect (Dificuldade ⭐⭐⭐)

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);
}

Resultado:

TEXT 📖 Somente leitura
--- 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

Combinadores de iteradores são uma das aplicações mais poderosas dos closures: map (transformar cada elemento), filter (retirar elementos com base em condições), flat_map (achatar iteradores aninhados), any/all (verificações de agregação), count (contagem). O valor dos closures aqui reside em sua capacidade de capturar valores dinâmicos externos (como min_rating, weight e category), desacoplando a lógica de processamento de dados da configuração. Chamadas encadeadas iter().filter().map().collect() estão entre as expressões mais expressivas em Rust.


▶ Exemplo 5: Exercício abrangente — Implementação de um pipeline de dados orientado por configuração usando closures (Dificuldade ⭐⭐⭐)

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);
}

Resultado:

TEXT 📖 Somente leitura
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

DataPipeline usa Box<dyn Fn> para armazenar closures, move captura valores de configuração externos (min_score, bonus), e as chamadas encadeadas para filter().map() constroem o pipeline de processamento. Os closures permitem “separar a configuração da lógica”.


❓ Perguntas Frequentes

P: Qual é a diferença fundamental entre closures e funções comuns? R: Os closures podem capturar variáveis do escopo em que estão inseridos; as funções comuns não. As funções comuns só podem receber dados por meio de parâmetros. Os closures podem “lembrar” e acessar variáveis externas no momento da definição — como se pegassem ingredientes diretamente da geladeira. Essa é a razão fundamental pela qual os closures existem.

P: Como o compilador escolhe entre Fn, FnMut e FnOnce? R: O compilador infere automaticamente com base na forma como o corpo do closure acessa as variáveis capturadas. Acesso somente leitura ➔ Fn, acesso mutável ➔ FnMut, consumo de propriedade ➔ FnOnce. Você também pode usar move para forçar FnOnce. Essas três características formam uma hierarquia: Fn herda FnMut, e FnMut herda FnOnce.

P: Quando se deve usar a palavra-chave move? R: Quando for necessário que o closure possua as variáveis capturadas, especialmente em cenários multithreading. Por exemplo, thread::spawn(move || { ... }) exige que o closure e todas as suas variáveis capturadas tenham uma duração 'static. move força as variáveis a serem movidas para dentro do closure, em vez de serem emprestadas. Cenários que não envolvem multithreading geralmente não precisam de move.

P: Os ponteiros de função fn(T) -> U e a característica de closure Fn(T) -> U podem ser usados de forma intercambiável? R: Não são totalmente intercambiáveis. Os ponteiros de função não podem capturar o ambiente; os closures, sim. No entanto, um ponteiro de função pode ser usado como um closure sem captura (o compilador o converte automaticamente). Se uma assinatura de função especificar fn(T) -> U, passar um closure com captura causará um erro de compilação; escrever impl Fn(T) -> U aceita ambos.

P: Ao usar closures como parâmetros, devo escolher impl Fn, Box<dyn Fn> ou o genérico F: Fn? R: A restrição genérica F: Fn apresenta o melhor desempenho (despacho estático), Box<dyn Fn> é a mais flexível (despacho dinâmico) e impl Fn é um sinônimo sintático para genéricos. Para caminhos em que o desempenho não é crítico, impl Fn é a melhor escolha. Use Box<dyn Fn> quando precisar armazenar ou retornar closures. As restrições genéricas são adequadas para cenários complexos que exigem múltiplas referências ao parâmetro de tipo.

P: Qual é o desempenho de um closure? Ele é mais lento do que uma função comum? R: Closures que não capturam o ambiente têm desempenho exatamente igual ao das funções comuns. Closures que capturam o ambiente têm sobrecarga zero ou quase zero. Os closures do Rust são “abstrações de custo zero” — closures sem escopo capturado são simplesmente ponteiros de função; para closures com escopo capturado, o compilador tentará incorporá-los sempre que possível. A sobrecarga de chamada indireta ocorre apenas ao usar Box<dyn Fn> para despacho dinâmico.


📖 Resumo


📝 Exercícios

  1. Dificuldade ⭐: Existe uma variável Vec<i32> que contém [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]. Escreva um closure que capture a variável threshold (definida como 5), use filter para filtrar os números maiores que threshold, use map para duplicá-los e, por fim, use collect para adicioná-los a um novo Vec e imprimir o resultado.

  2. Dificuldade ⭐⭐: Escreva uma função fn transform<F>(data: &[i32], f: F) -> Vec<i32> where F: Fn(i32) -> i32 que aplique uma função de transformação a cada elemento. Na função main, crie três closures: |x| x * 2 (double), um closure de adição que captura a variável add e um closure de truncamento que captura a variável max_val (retorna max_val se o valor exceder max_val). Chame transform para cada um deles e imprima os resultados.

  3. Dificuldade ⭐⭐⭐: Existe uma estrutura chamada struct Product { name: String, price: f64, category: String }. Crie 6 instâncias de produtos e armazene-as em um vetor. Escreva uma função fn analyze_products<F1, F2>(products: &[Product], category_filter: F1, price_adjuster: F2) where F1: Fn(&&Product) -> bool, F2: Fn(f64) -> f64 que primeiro filtre por categoria usando filter, depois ajuste o preço usando map e, por fim, classifique os produtos por seus preços ajustados e imprima os três produtos mais caros. Na função main, teste isso pelo menos duas vezes usando diferentes estratégias de filtragem e ajuste de preço.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%