Rust Admin

Closures em Rust: Fn, FnMut e FnOnce na Prática na Prática Já leu

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 , e é 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 é o trait mais restritivo. Closures que implementam podem ser chamados múltiplas vezes e apenas leem as variáveis capturadas sem

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.

Referências


Artigos relacionados