Closures

Closures ou expressões lambda têm tipos que não podem ser nomeados. No entanto, eles implementam os traits especiais Fn, FnMut e FnOnce:

fn apply_with_log(func: impl FnOnce(i32) -> i32, input: i32) -> i32 {
    println!("Chamando a função com {input}");
    func(input)
}

fn main() {
    let add_3 = |x| x + 3;
    println!("add_3: {}", apply_with_log(add_3, 10));
    println!("add_3: {}", apply_with_log(add_3, 20));

    let mut v = Vec::new();
    let mut accumulate = |x: i32| {
        v.push(x);
        v.iter().sum::<i32>()
    };
    println!("accumulate: {}", apply_with_log(&mut accumulate, 4));
    println!("accumulate: {}", apply_with_log(&mut accumulate, 5));

    let multiply_sum = |x| x * v.into_iter().sum::<i32>();
    println!("multiply_sum: {}", apply_with_log(multiply_sum, 3));
}
This slide should take about 10 minutes.

Um Fn (p.ex. add_3) não consome nem muda os valores capturados ou talvez não capture nada, podendo então ser chamado várias vezes simultaneamente.

Um FnMut (p.ex. accumulate) pode alterar os valores capturados, logo você pode chamá-lo várias vezes, mas não simultaneamente.

Se você tiver um FnOnce (p.ex. multiply_sum), poderá chamá-lo apenas uma vez. Ele pode consumir os valores capturados.

FnMut é um subtipo de FnOnce. Fn é um subtipo de FnMut e FnOnce. Ou seja, você pode usar um FnMut sempre que um FnOnce é chamado e você pode usar um Fn sempre que um FnMut ou um FnOnce é chamado.

Quando você define uma função que recebe um closure, você deve usar FnOnce se puder (ou seja, você o chama uma vez) ou FnMut caso contrário, e por último Fn. Isso permite a maior flexibilidade para o chamador.

Em contraste, quando você tem um closure, o mais flexível que você pode ter é Fn (ele pode ser passado para qualquer lugar), então FnMut e, por último, FnOnce.

O compilador também infere Copy (p.ex. para add_3) e Clone (p.ex.

Por padrão, os closures capturam por referência se puderem. A palavra-chave move faz com que eles capturem por valor.

fn make_greeter(prefix: String) -> impl Fn(&str) {
    return move |name| println!("{} {}", prefix, name);
}

fn main() {
    let hi = make_greeter("Olá".to_string());
    hi("Greg");
}