Closures em Rust: Fn, FnMut e FnOnce | Rust Brasil

Guia completo de closures em Rust: Fn, FnMut, FnOnce, captura de variáveis, move closures e uso com iteradores.

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