Closures são funções anônimas que podem capturar variáveis do ambiente ao redor. Em Rust, elas são fundamentais para a programação funcional e estão por toda parte: em iteradores, callbacks, handlers assíncronos e muito mais. Mas, diferente de outras linguagens, o sistema de closures em Rust é intimamente ligado ao ownership — e entender como as closures capturam variáveis é essencial para usá-las com maestria.
O Básico: Anatomia de uma Closure
fn main() {
// Função regular
fn somar_fn(a: i32, b: i32) -> i32 { a + b }
// Closure equivalente
let somar_cl = |a: i32, b: i32| -> i32 { a + b };
// Closure com inferência de tipos (mais comum)
let somar = |a, b| a + b;
println!("{}", somar_fn(2, 3)); // 5
println!("{}", somar_cl(2, 3)); // 5
println!("{}", somar(2, 3)); // 5
}
A grande diferença entre closures e funções: closures podem capturar variáveis do escopo:
fn main() {
let multiplicador = 3;
// A closure captura 'multiplicador' do ambiente
let multiplicar = |x| x * multiplicador;
println!("{}", multiplicar(5)); // 15
println!("{}", multiplicar(10)); // 30
}
Modos de Captura
Rust escolhe automaticamente como capturar cada variável, usando o modo menos restritivo possível:
1. Por referência imutável (&T)
fn main() {
let nome = String::from("Rust");
// Captura &nome — apenas lê o valor
let saudacao = || println!("Olá, {}!", nome);
saudacao();
saudacao();
// 'nome' ainda é acessível — não foi movido
println!("Nome original: {}", nome);
}
2. Por referência mutável (&mut T)
fn main() {
let mut contador = 0;
// Captura &mut contador — modifica o valor
let mut incrementar = || {
contador += 1;
println!("Contador: {}", contador);
};
incrementar(); // Contador: 1
incrementar(); // Contador: 2
incrementar(); // Contador: 3
// 'contador' está acessível novamente após a closure sair do escopo
println!("Final: {}", contador); // Final: 3
}
3. Por valor (move)
fn main() {
let dados = vec![1, 2, 3];
// Captura dados por valor — a closure toma ownership
let consumir = || {
let soma: i32 = dados.into_iter().sum();
println!("Soma: {}", soma);
};
consumir();
// consumir(); // ERRO: já foi consumida (FnOnce)
// println!("{:?}", dados); // ERRO: dados foi movido para a closure
}
Diagrama de Captura
┌─────────────────────────────────────────────────────┐
│ Modos de Captura │
├─────────────┬──────────────┬────────────────────────┤
│ Modo │ Quando │ Trait Implementada │
├─────────────┼──────────────┼────────────────────────┤
│ &T │ Apenas lê │ Fn (+ FnMut + FnOnce) │
│ &mut T │ Modifica │ FnMut (+ FnOnce) │
│ T (move) │ Consome/move │ FnOnce │
└─────────────┴──────────────┴────────────────────────┘
Hierarquia: FnOnce ⊇ FnMut ⊇ Fn
(menos) (médio) (mais restritivo)
Os Três Traits: Fn, FnMut, FnOnce
Fn — Pode ser chamada múltiplas vezes, sem mutar capturas
fn aplicar_varias_vezes<F: Fn(i32) -> i32>(f: F, valores: &[i32]) -> Vec<i32> {
valores.iter().map(|v| f(*v)).collect()
}
fn main() {
let fator = 2;
let dobrar = |x| x * fator; // captura fator por &
let resultado = aplicar_varias_vezes(dobrar, &[1, 2, 3, 4, 5]);
println!("{:?}", resultado); // [2, 4, 6, 8, 10]
// dobrar ainda pode ser usada
println!("Dobro de 10: {}", dobrar(10));
}
FnMut — Pode ser chamada múltiplas vezes, pode mutar capturas
fn aplicar_com_estado<F: FnMut(i32)>(mut f: F, valores: &[i32]) {
for v in valores {
f(*v);
}
}
fn main() {
let mut soma = 0;
let mut acumular = |x| soma += x; // captura &mut soma
aplicar_com_estado(&mut acumular, &[1, 2, 3, 4, 5]);
drop(acumular); // libera o empréstimo mutável
println!("Soma: {}", soma); // Soma: 15
}
FnOnce — Pode ser chamada apenas uma vez (consome as capturas)
fn executar_uma_vez<F: FnOnce() -> String>(f: F) -> String {
f() // consume f
}
fn main() {
let nome = String::from("Rust Brasil");
// 'nome' será movido para dentro da closure
let saudacao = || format!("Bem-vindo ao {}!", nome);
let resultado = executar_uma_vez(saudacao);
println!("{}", resultado);
// saudacao(); // ERRO: já foi consumida
}
move Closures
A palavra-chave move força a closure a tomar ownership de todas as variáveis capturadas:
use std::thread;
fn main() {
let mensagem = String::from("Olá da thread!");
// move é necessário aqui porque a thread pode outlive o escopo atual
let handle = thread::spawn(move || {
println!("{}", mensagem);
});
// println!("{}", mensagem); // ERRO: mensagem foi movida
handle.join().unwrap();
}
move com tipos Copy
Para tipos que implementam Copy, move cria uma cópia:
fn main() {
let x = 42; // i32 implementa Copy
let closure = move || println!("x = {}", x);
closure();
println!("x ainda acessível: {}", x); // OK — foi copiado, não movido
}
move para captura parcial
fn main() {
let nome = String::from("Ana");
let idade = 30u32;
// move captura ambos, mas idade é copiada (é Copy)
// e nome é movida (é String, não Copy)
let descrever = move || {
format!("{} tem {} anos", nome, idade)
};
println!("{}", descrever());
println!("Idade ainda acessível: {}", idade); // OK, foi copiada
// println!("{}", nome); // ERRO: nome foi movida
}
Closures como Parâmetros de Funções
Com Generics (dispatch estático)
fn filtrar_e_transformar<P, T>(
dados: &[i32],
predicado: P,
transformar: T,
) -> Vec<String>
where
P: Fn(&i32) -> bool,
T: Fn(i32) -> String,
{
dados
.iter()
.filter(|x| predicado(x))
.map(|x| transformar(*x))
.collect()
}
fn main() {
let numeros = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let resultado = filtrar_e_transformar(
&numeros,
|x| *x % 2 == 0, // predicado: apenas pares
|x| format!("{} ao quadrado = {}", x, x * x), // transformar
);
for r in resultado {
println!("{}", r);
}
}
Com dyn (dispatch dinâmico)
struct Pipeline {
etapas: Vec<Box<dyn Fn(String) -> String>>,
}
impl Pipeline {
fn novo() -> Self {
Pipeline { etapas: Vec::new() }
}
fn adicionar(mut self, etapa: impl Fn(String) -> String + 'static) -> Self {
self.etapas.push(Box::new(etapa));
self
}
fn executar(&self, input: String) -> String {
self.etapas.iter().fold(input, |acc, etapa| etapa(acc))
}
}
fn main() {
let pipeline = Pipeline::novo()
.adicionar(|s| s.trim().to_string())
.adicionar(|s| s.to_uppercase())
.adicionar(|s| format!("[{}]", s));
let resultado = pipeline.executar(" olá, rust! ".to_string());
println!("{}", resultado); // [OLÁ, RUST!]
}
Closures como Tipo de Retorno
Com impl Fn (quando o tipo é único)
fn criar_multiplicador(fator: i32) -> impl Fn(i32) -> i32 {
move |x| x * fator
}
fn criar_saudacao(idioma: &str) -> impl Fn(&str) -> String + '_ {
move |nome| match idioma {
"pt" => format!("Olá, {}!", nome),
"en" => format!("Hello, {}!", nome),
"es" => format!("¡Hola, {}!", nome),
_ => format!("Hi, {}!", nome),
}
}
fn main() {
let triplicar = criar_multiplicador(3);
println!("{}", triplicar(7)); // 21
let saudar_pt = criar_saudacao("pt");
println!("{}", saudar_pt("Maria")); // Olá, Maria!
}
Com Box<dyn Fn> (quando o tipo varia)
fn criar_operacao(tipo: &str) -> Box<dyn Fn(f64, f64) -> f64> {
match tipo {
"somar" => Box::new(|a, b| a + b),
"subtrair" => Box::new(|a, b| a - b),
"multiplicar" => Box::new(|a, b| a * b),
"dividir" => Box::new(|a, b| {
if b == 0.0 { f64::NAN } else { a / b }
}),
_ => Box::new(|_, _| 0.0),
}
}
fn main() {
let operacoes = vec!["somar", "subtrair", "multiplicar", "dividir"];
for op_nome in &operacoes {
let op = criar_operacao(op_nome);
println!("{}: 10 op 3 = {:.2}", op_nome, op(10.0, 3.0));
}
}
Padrões Avançados
Closure que retorna closure
fn compor<A, B, C>(
f: impl Fn(A) -> B + 'static,
g: impl Fn(B) -> C + 'static,
) -> impl Fn(A) -> C
where
A: 'static,
B: 'static,
C: 'static,
{
move |x| g(f(x))
}
fn main() {
let dobrar = |x: i32| x * 2;
let adicionar_um = |x: i32| x + 1;
let para_string = |x: i32| format!("resultado: {}", x);
let transformar = compor(compor(dobrar, adicionar_um), para_string);
println!("{}", transformar(5)); // resultado: 11
}
Closures com estado mutável (simulando objetos)
fn criar_contador(inicio: i32) -> impl FnMut() -> i32 {
let mut valor = inicio;
move || {
let atual = valor;
valor += 1;
atual
}
}
fn main() {
let mut contador = criar_contador(1);
println!("{}", contador()); // 1
println!("{}", contador()); // 2
println!("{}", contador()); // 3
// Cada instância tem estado independente
let mut outro = criar_contador(100);
println!("{}", outro()); // 100
println!("{}", outro()); // 101
}
Callbacks e Event Handlers
struct Botao {
rotulo: String,
on_click: Option<Box<dyn FnMut()>>,
}
impl Botao {
fn novo(rotulo: &str) -> Self {
Botao {
rotulo: rotulo.to_string(),
on_click: None,
}
}
fn ao_clicar(mut self, handler: impl FnMut() + 'static) -> Self {
self.on_click = Some(Box::new(handler));
self
}
fn clicar(&mut self) {
println!("Botão '{}' clicado", self.rotulo);
if let Some(ref mut handler) = self.on_click {
handler();
}
}
}
fn main() {
let mut cliques = 0;
let mut botao = Botao::novo("Enviar")
.ao_clicar(move || {
cliques += 1;
println!(" Handler executado ({} vezes)", cliques);
});
botao.clicar(); // Handler executado (1 vezes)
botao.clicar(); // Handler executado (2 vezes)
botao.clicar(); // Handler executado (3 vezes)
}
Erros Comuns
1. Captura inesperada por move
fn main() {
let texto = String::from("importante");
// A closure consome 'texto' porque usa .into_bytes()
let processar = || {
let bytes = texto.into_bytes(); // move de texto
println!("{} bytes", bytes.len());
};
processar();
// processar(); // ERRO: texto já foi movido
// SOLUÇÃO: clone se precisar reusar
let texto2 = String::from("importante");
let processar2 = {
let texto_clone = texto2.clone();
move || {
let bytes = texto_clone.clone().into_bytes();
println!("{} bytes", bytes.len());
}
};
processar2();
processar2(); // OK!
println!("Original: {}", texto2); // OK!
}
2. Lifetime de closure que captura referência
// NÃO COMPILA: retorna closure com referência a dado local
// fn criar_saudacao_errada() -> impl Fn() {
// let nome = String::from("Rust");
// || println!("Olá, {}!", nome) // nome é destruído ao final
// }
// SOLUÇÃO: use move para tomar ownership
fn criar_saudacao_correta() -> impl Fn() {
let nome = String::from("Rust");
move || println!("Olá, {}!", nome) // move nome para dentro da closure
}
fn main() {
let saudar = criar_saudacao_correta();
saudar();
}
3. Confundir Fn e FnMut em trait bounds
fn main() {
let mut total = 0;
// Esta closure precisa de FnMut (modifica total)
let mut acumular = |x: i32| { total += x; };
// Se uma função espera Fn, não aceita FnMut:
// fn so_fn(f: impl Fn(i32)) { f(1); f(2); }
// so_fn(acumular); // ERRO: expected Fn, found FnMut
// SOLUÇÃO: peça FnMut quando a closure pode mutar
fn aceita_fn_mut(mut f: impl FnMut(i32)) {
f(1);
f(2);
}
aceita_fn_mut(&mut acumular);
drop(acumular);
println!("Total: {}", total); // Total: 3
}
Aplicação no Mundo Real: Middleware Pattern
type Handler = Box<dyn Fn(&str) -> String>;
type Middleware = Box<dyn Fn(Handler) -> Handler>;
fn criar_logger() -> Middleware {
Box::new(|proximo: Handler| -> Handler {
Box::new(move |req: &str| -> String {
println!("[LOG] Requisição: {}", req);
let resposta = proximo(req);
println!("[LOG] Resposta: {}", resposta);
resposta
})
})
}
fn criar_auth() -> Middleware {
Box::new(|proximo: Handler| -> Handler {
Box::new(move |req: &str| -> String {
if req.starts_with("AUTH:") {
proximo(&req[5..])
} else {
"401 Não autorizado".to_string()
}
})
})
}
fn handler_final(req: &str) -> String {
format!("200 OK: processado '{}'", req)
}
fn aplicar_middlewares(handler: Handler, middlewares: Vec<Middleware>) -> Handler {
middlewares
.into_iter()
.rev()
.fold(handler, |h, m| m(h))
}
fn main() {
let handler = aplicar_middlewares(
Box::new(handler_final),
vec![criar_logger(), criar_auth()],
);
println!("---");
println!("Resultado: {}", handler("AUTH:dados importantes"));
println!("---");
println!("Resultado: {}", handler("dados sem auth"));
}
Veja Também
- Como Filtrar Vetor em Rust — closures na prática com iteradores
- Iteradores em Rust — closures são essenciais para combinadores
- Trait Objects vs Generics —
impl Fnvsdyn Fnem detalhes - Entendendo Lifetimes em Rust — lifetimes em closures capturadas
- Async/Await em Rust — closures async e futures