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):

  1. 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.
  2. 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

  1. Tentar Chamar FnOnce Mú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.
  2. Esquecer move ao Criar Threads (E0373):
    • closure may outlive the current function, but it borrows data.
    • Correção: Adicione a palavra-chave move antes 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);