Capítulo 14: Closures, Functores e Programação Funcional
Capítulo 14: Closures, Functores e Programação Funcional
Especialização em Sistemas com Rust • Rust 2021 Edition • Ownership, Concorrência Segura e Sistemas de Alta Performance
🗺️ Mapa Conceitual da Hierarquia de Traits de Closures
flowchart TD
Closure["Closure Anônima |args| { corpo }"] --> Captura["Análise da Captura de Ambiente"]
subgraph TRAIT_HIERARCHY["Hierarquia de Traits de Invocação"]
FnOnce["FnOnce: Captura por Posse (Consome o ambiente / Executa 1 vez)"]
FnMut["FnMut: Captura por Referência Mutável (&mut T)"]
Fn["Fn: Captura por Referência Imutável (&T)"]
end
Captura --> FnOnce
FnOnce ---|"FnMut é subtrait de FnOnce"| FnMut
FnMut ---|"Fn é subtrait de FnMut"| Fn
subgraph MODIFICADOR["Palavra-Chave move"]
MoveKeyword["move: Força Transferência de Ownership de Variáveis Capturadas"]
ThreadSafe["Mandatório para disparar threads (thread::spawn)"]
end
Closure --> MoveKeyword --> ThreadSafe
style Closure fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style Captura fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style TRAIT_HIERARCHY fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style Fn fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style MoveKeyword fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
🏛️ 1. O que São Closures no Rust?
No Rust, uma Closure é uma função anônima que você pode salvar em uma variável, passar como argumento para outras funções ou retornar de rotinas.
Diferente de funções normais (fn):
- Inferência Total de Tipos: Você geralmente não precisa anotar os tipos dos parâmetros nem do retorno; o compilador deduz os tipos a partir do primeiro uso.
- Captura de Ambiente (Environment Capture): Closures podem ler, mutar ou assumir a posse de variáveis que foram declaradas no escopo onde a closure foi definida, sem que você precise passá-las explicitamente como argumentos.
⚙️ 2. As Três Traits de Invocação: Fn, FnMut e FnOnce
O compilador avalia como a closure utiliza as variáveis capturadas e implementa automaticamente uma ou mais das três traits fundamentais:
2.1 FnOnce (Execução Única por Consumo)
A closure consome as variáveis capturadas assumindo sua posse (takes ownership). Como o ambiente é consumido na primeira invocação, a closure só pode ser chamada uma única vez:
let chave = String::from("SEGREDO_HEX");
// Consome 'chave' movendo-a para dentro da closure
let consumir_segredo = || {
println!("Consumindo chave: {}", chave);
drop(chave); // Destrói o recurso
};
consumir_segredo(); // OK!
// consumir_segredo(); // ERRO DE COMPILAÇÃO: use of moved value!
2.2 FnMut (Mutação do Ambiente)
A closure muta o ambiente capturado através de referências mutáveis (&mut). Ela pode ser chamada múltiplas vezes, alterando seu estado interno a cada execução:
let mut total_erros = 0;
let mut registrar_falha = || {
total_erros += 1; // Muta o ambiente capturado
println!("Falha registrada. Total acumulado: {}", total_erros);
};
registrar_falha(); // total_erros = 1
registrar_falha(); // total_erros = 2
2.3 Fn (Leitura Imutável Pura)
A closure apenas lê o ambiente por referências imutáveis (&) ou não captura nada. Pode ser chamada infinitas vezes simultaneamente por múltiplas threads sem travas:
let prefixo = "LOG_INFO";
let formatar = |msg: &str| format!("{}: {}", prefixo, msg);
println!("{}", formatar("Serviço iniciado"));
println!("{}", formatar("Porta 8080 aberta"));
🚚 3. A Palavra-Chave move
Por padrão, o Rust captura variáveis pelo menor privilégio possível (referência imutável se apenas ler, mutável se alterar).
No entanto, quando você precisa passar uma closure para outra Thread do sistema operacional (thread::spawn) ou retorná-la de uma função, as referências locais deixariam de existir. A palavra-chave move instrui o compilador a transferir a propriedade (ownership) de todas as variáveis capturadas para dentro da closure:
use std::thread;
let dados_thread = String::from("METRICAS_NUCLEO");
// 'move' transfere a posse de 'dados_thread' para a thread filha
let handle = thread::spawn(move || {
println!("Processando na thread independente: {}", dados_thread);
// 'dados_thread' é liberada na thread filha
});
handle.join().unwrap();
// println!("{}", dados_thread); // ERRO: valor movido para a thread!
🧩 4. Funções de Alta Ordem (Higher-Order Functions)
Você pode projetar funções genéricas que recebem closures como parâmetros utilizando Trait Bounds:
fn medir_tempo_execucao<F, T>(operacao: F) -> T
where
F: FnOnce() -> T,
{
let inicio = std::time::Instant::now();
let resultado = operacao();
let duracao = inicio.elapsed();
println!("Tempo decorrido: {:?}", duracao);
resultado
}
⚖️ 5. Comparativo: Fn vs FnMut vs FnOnce
| Trait | Modo de Captura | Quantas Vezes Pode Executar? | Subtrait De… |
|---|---|---|---|
FnOnce |
Por Posse / Valor (T) |
Exatamente 1 vez | (Nenhuma - Raiz) |
FnMut |
Por Referência Mutável (&mut T) |
Múltiplas vezes em sequência | FnOnce |
Fn |
Por Referência Imutável (&T) |
Infinitas vezes (concorrente) | FnMut + FnOnce |
Nota Arquitetural: Toda closure que implementa Fn também implementa FnMut e FnOnce. Qualquer função que aceite FnOnce aceita também closures Fn e FnMut.
⚠️ 6. Armadilhas Comuns e Mensagens do Compilador
- Tentar Chamar
FnOnceMúltiplas Vezes (E0382):- O compilador rejeitará:
use of moved value: closure. - Correção: Se a closure precisa ser invocada em loop, ela não pode consumir o ownership de variáveis externas.
- O compilador rejeitará:
- Esquecer
moveao Criar Threads (E0373):closure may outlive the current function, but it borrows data.- Correção: Adicione a palavra-chave
moveantes dos pipes:thread::spawn(move || { ... }).
🛡️ 7. Boas Práticas de Engenharia Funcional
- Evite Closures Monolíticas: Mantenha suas closures curtas e com responsabilidade única, ideais para serem passadas a iteradores (
.map(),.filter()). - Use
impl Fn(...)no Retorno: Ao retornar uma closure de uma função, utilize a sintaxe de tipo implícito:fn criar_somador(k: i32) -> impl Fn(i32) -> i32 { move |x| x + k }.
📋 8. Cheat-Sheet: Resumo Executivo
// Sintaxe básica
let dobro = |x: i32| x * 2;
// Captura com mutação (FnMut)
let mut count = 0;
let mut inc = || count += 1;
// Captura com transferência de posse (move)
let s = String::from("Hi");
let consome = move || println!("{}", s);