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
- Como escrever a sintaxe de closures
|params| expre|params| { block } - Três modos de captura:
Fn(empréstimo imutável),FnMut(empréstimo mutável),FnOnce(consumo de propriedade) movePalavra-chave — Faz com que a propriedade das variáveis capturadas seja transferida para o closure- Closures como argumentos de função: a diferença entre ponteiros de função
fne a característicaFn - Combinação de closures e iteradores: chamadas encadeadas
map,filter,collect - Padrões para o uso de closures em cenários reais
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):
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!("{ }", 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:
- Usuários regulares: Avaliem diretamente com 1 a 5 estrelas
- Blogueiro de culinária: Pontuação × 2 (ponderação maior)
- Inspetor sanitário: pontuações inferiores a 3 são classificadas como “Precisa de correção”
No início, ela escreveu uma função para cada regra:
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.”
// 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
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:
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 * weightcaptura variáveis externasweight— algo que as funções comuns não conseguem fazer.weightethresholdpodem 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
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 ⭐)
// ============================================
// 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:
--- 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_passcapturamin_rating, eweighted_scorercapturaweight. A funçãocheck_reviewsaceitaimpl Fn(&Review) -> u32— qualquer closure que implemente a característicaFnpode ser passado para ela.
▶ Exemplo 2: Modo de captura — Fn / FnMut / FnOnce (Dificuldade ⭐⭐)
// ============================================
// 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:
=== 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 leitura ➔
Fn, acesso mutável ➔FnMut(o closure deve ser declarado commut), consumo de propriedade ➔FnOnce(só pode ser chamado uma vez). A palavra-chavemoveforça a transferência de propriedade para o closure — comumente usada em cenários multithreading (thread::spawnrequer o tempo de vida'static).
▶ Exemplo 3: Closures como argumentos — Ponteiros de função x a característica Fn (Dificuldade ⭐⭐)
// ============================================
// 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:
--- 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) -> i32só podem aceitar funções comuns e closures que não capturam o ambiente. A característicaFn(genérica) pode aceitar qualquer closure (incluindo aqueles que capturam o ambiente). Regras de seleção: Use ponteirosfnpara FFI ou quando for necessário armazenar um tipo de função fixo; aceite traçosFn/FnMut/FnOncepara cenários que exijam closures que capturem o ambiente. As funçõesrun_strategyaceitamimpl Fn(u32) -> u32— quatro estratégias de pontuação diferentes — como closures, incluindo estratégias de bônus que capturambonus_thresholdebonus_points.
▶ Exemplo 4: Combinando closures e iteradores — map / filter / collect (Dificuldade ⭐⭐⭐)
// ============================================
// 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:
--- 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 (comomin_rating,weightecategory), desacoplando a lógica de processamento de dados da configuração. Chamadas encadeadasiter().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 ⭐⭐⭐)
// ============================================
// 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:
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
DataPipelineusaBox<dyn Fn>para armazenar closures,movecaptura valores de configuração externos (min_score,bonus), e as chamadas encadeadas parafilter().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 usarmovepara forçarFnOnce. Essas três características formam uma hierarquia:FnherdaFnMut, eFnMutherdaFnOnce.
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.moveforç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 demove.
P: Os ponteiros de função
fn(T) -> Ue a característica de closureFn(T) -> Upodem 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 especificarfn(T) -> U, passar um closure com captura causará um erro de compilação; escreverimpl Fn(T) -> Uaceita ambos.
P: Ao usar closures como parâmetros, devo escolher
impl Fn,Box<dyn Fn>ou o genéricoF: Fn? R: A restrição genéricaF: Fnapresenta o melhor desempenho (despacho estático),Box<dyn Fn>é a mais flexível (despacho dinâmico) eimpl Fné um sinônimo sintático para genéricos. Para caminhos em que o desempenho não é crítico,impl Fné a melhor escolha. UseBox<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
- Sintaxe de fechamento
|params| exprou|params| { block }; os tipos dos parâmetros e do valor de retorno são geralmente inferidos - Três modos de captura:
Fn(empréstimo imutável, pode ser chamado várias vezes),FnMut(empréstimo mutável, pode ser chamado várias vezes),FnOnce(consome a propriedade, só pode ser chamado uma vez) moveA palavra-chave faz com que a propriedade das variáveis capturadas seja transferida para o closure — uma prática padrão na programação multithread- Os parâmetros de função podem aceitar closures: ponteiros
fn(sem captura) ou traitsFn/FnMut/FnOnce(genéricos) - A combinação de closure e iterador (
map,filter,flat_map,any,all) é uma das expressões mais poderosas da Rust - Os closures do Rust são abstrações de custo zero — incorporados em tempo de compilação, sem nenhuma sobrecarga em tempo de execução
📝 Exercícios
-
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ávelthreshold(definida como 5), usefilterpara filtrar os números maiores quethreshold, usemappara duplicá-los e, por fim, usecollectpara adicioná-los a um novo Vec e imprimir o resultado. -
Dificuldade ⭐⭐: Escreva uma função
fn transform<F>(data: &[i32], f: F) -> Vec<i32> where F: Fn(i32) -> i32que aplique uma função de transformação a cada elemento. Na funçãomain, crie três closures:|x| x * 2(double), um closure de adição que captura a variáveladde um closure de truncamento que captura a variávelmax_val(retornamax_valse o valor excedermax_val). Chametransformpara cada um deles e imprima os resultados. -
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çãofn analyze_products<F1, F2>(products: &[Product], category_filter: F1, price_adjuster: F2) where F1: Fn(&&Product) -> bool, F2: Fn(f64) -> f64que primeiro filtre por categoria usandofilter, depois ajuste o preço usandomape, por fim, classifique os produtos por seus preços ajustados e imprima os três produtos mais caros. Na funçãomain, teste isso pelo menos duas vezes usando diferentes estratégias de filtragem e ajuste de preço.