O que são Closures em Rust?
Closures são funções anônimas que podem capturar variáveis do escopo ao seu redor. Diferente de funções regulares, closures mantêm referências ou propriedade das variáveis externas, permitindo um padrão funcional poderoso. Em Rust, o tipo de closure é determinado automaticamente pelo compilador baseado em como ele usa as variáveis capturadas. A distinção entre Fn, FnMut e FnOnce é fundamental para entender como closures interagem com a memória e o ownership do Rust.
O sistema de traits de closures foi projetado para oferecer máxima flexibilidade mantendo segurança de memória. Quando você cria um closure, o compilador analisa quais variáveis são capturadas e como são usadas, determinando automaticamente qual trait implementar. Esta análise acontece em tempo de compilação, não em runtime, garantindo zero overhead e máxima otimização.
Entendendo os Três Traits: Fn, FnMut e FnOnce
Fn — Apenas Leitura
Fn é o trait mais restritivo. Closures que implementam Fn podem ser chamados múltiplas vezes e apenas leem as variáveis capturadas sem modificá-las ou consumi-las. O closure recebe referências imutáveis (&T) das variáveis externas.
fn main() {
let multiplier = 5;
let multiply = |x| x * multiplier; // Captura multiplier como &i32
println!("{}", multiply(3)); // 15
println!("{}", multiply(4)); // 20
// Pode ser chamado infinitas vezes
let result: Vec<i32> = vec![1, 2, 3]
.iter()
.map(|&x| multiply(x))
.collect();
}
FnMut — Leitura e Escrita
FnMut permite que o closure modifique as variáveis capturadas, mas não as consome. Recebe referências mutáveis (&mut T). Pode ser chamado múltiplas vezes, mas não simultaneamente em diferentes threads de forma segura.
fn main() {
let mut counter = 0;
let mut increment = || {
counter += 1; // Modifica counter
counter
};
println!("{}", increment()); // 1
println!("{}", increment()); // 2
println!("{}", increment()); // 3
// Passando FnMut para uma função
apply_twice(&mut increment);
}
fn apply_twice<F>(f: &mut F)
where
F: FnMut(),
{
f();
f();
}
FnOnce — Consumo Total
FnOnce consome as variáveis capturadas, transferindo sua propriedade para dentro do closure. Pode ser chamado apenas uma vez. Use quando o closure precisa tomar posse dos dados capturados.
fn main() {
let name = String::from("Rust");
let greeting = || {
println!("Olá, {}", name); // Consome name
};
greeting(); // Primeira chamada
// greeting(); // Erro: já foi consumido
// Exemplo com uma thread
let value = String::from("dados importantes");
std::thread::spawn(move || {
println!("Thread usa: {}", value); // move transfere propriedade
}).join().unwrap();
// println!("{}", value); // Erro: value foi movido
}
Hierarquia e Coerção de Traits
Existe uma relação hierárquica entre estes traits: todo Fn é também FnMut, e todo FnMut é também FnOnce. Isto significa que você pode passar um Fn onde FnMut ou FnOnce é esperado, mas não o contrário.
fn execute_fn<F>(f: F)
where
F: Fn(i32) -> i32,
{
println!("Resultado: {}", f(5));
}
fn execute_fn_mut<F>(mut f: F)
where
F: FnMut(i32) -> i32,
{
println!("Primeiro: {}", f(5));
println!("Segundo: {}", f(10));
}
fn main() {
let add_three = |x| x + 3; // Implementa Fn
execute_fn(add_three); // OK: Fn pode ser usado como Fn
execute_fn_mut(add_three); // OK: Fn pode ser usado como FnMut
let mut sum = 0;
let adder = |x| {
sum += x; // Implementa FnMut
sum
};
execute_fn_mut(adder); // OK: FnMut pode ser usado como FnMut
// execute_fn(adder); // Erro: FnMut não pode ser Fn
}
Casos Práticos de Uso
Closures são essenciais para padrões funcionais e callbacks em Rust. A escolha do trait correto impacta a flexibilidade e performance do seu código. Iteradores usam closures extensivamente: map requer Fn, enquanto for_each permite FnMut.
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let multiplier = 10;
// map usa Fn — não modifica estado externo
let doubled: Vec<i32> = numbers
.iter()
.map(|x| x * multiplier)
.collect();
let mut total = 0;
// for_each usa FnMut — pode modificar total
numbers.iter().for_each(|&x| {
total += x;
});
println!("Somados: {}", total); // 15
// Processando Strings com FnOnce
let process_data = |data: String| {
data.to_uppercase()
};
let result = process_data(String::from("rust"));
println!("{}", result); // RUST
}
Conclusão
Os três traits de closures em Rust — Fn, FnMut e FnOnce — refletem diferentes níveis de controle sobre dados capturados. Primeiro aprendizado: escolha Fn quando apenas ler variáveis; FnMut quando modificá-las; FnOnce quando consumir. Segundo aprendizado: esta hierarquia não é apenas sintaxe, mas um reflexo direto do sistema de ownership, oferecendo segurança em tempo de compilação. Terceiro aprendizado: dominar estes conceitos abre as portas para programação funcional elegante em Rust, essencial para iteradores, threads e APIs modernas.