Quem, diabos, são Fn, FnMut e FnOnce? Closures do Rust sob o microscópio

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Closures em Rust: magia ou matemática rigorosa?

\n\n

Se você já escreveu algo como |x| x + 1 em Rust, você usou closures. Mas assim que você tenta passar essa closure para uma função ou armazená-la em uma struct, o compilador de repente começa a exigir: “Qual trait? Fn, FnMut ou FnOnce?”.

\n\n

E é aí que muitos ficam confusos. Especialmente se você vem de JavaScript, Python ou Ruby, onde closures são “apenas funções que capturam variáveis”. Em Rust é mais complexo (e mais interessante).

\n\n

Hoje vamos entender o que realmente acontece por baixo dos panos quando o Rust decide qual trait dar à sua closure. Não é magia — é um sistema de tipos rigoroso que protege você de condições de corrida e ponteiros soltos. Vamos nessa!

\n\n

Resumindo: as três traits de closures

\n\n

Em Rust, existem três traits que as closures implementam:

\n\n
    \n
  • FnOnce — pode ser chamada uma vez. Assume a propriedade das variáveis capturadas.
  • \n
  • FnMut — pode ser chamada múltiplas vezes, mas pode modificar as variáveis capturadas (usa &mut).
  • \n
  • Fn — pode ser chamada múltiplas vezes e não modifica as variáveis capturadas (usa &).
  • \n
\n\n

Cada closure implementa automaticamente todas as traits aplicáveis. Ou seja, se uma closure implementa Fn, ela automaticamente implementa também FnMut e FnOnce. Mas o contrário não funciona.

\n\n

Por quê? Porque Fn é a mais restritiva, e FnOnce é a mais fraca. A hierarquia de herança é assim:

\n\n
FnOnce  ←  FnMut  ←  Fn
\n\n

Ou seja, FnMut: FnOnce, e Fn: FnMut. Lembre-se dessa escada — ela é fundamental.

\n\n

Como o Rust decide qual trait dar à closure?

\n\n

O compilador observa como exatamente a closure usa as variáveis capturadas. Existem três modos de captura:

\n\n
    \n
  • Por referência (&T) — a closure apenas lê a variável.
  • \n
  • Por referência mutável (&mut T) — a closure modifica a variável.
  • \n
  • Por valor (T) — a closure assume a propriedade da variável.
  • \n
\n\n

Veja como isso afeta as traits:

\n\n\n \n \n \n \n
Modo de capturaTraits implementadasExemplo
Apenas &TFn + FnMut + FnOnce|| println!("{}", x)
&mut T (mas não T)FnMut + FnOnce|| x += 1
T (propriedade)Apenas FnOnce|| drop(x) ou move || x
\n\n

Vamos analisar cada caso com código real.

\n\n

Exemplo 1: Apenas leitura → Fn

\n\n
let x = 42;\nlet read_closure = || println!("x = {}", x);\n\n// Pode ser chamada quantas vezes quiser\nread_closure();\nread_closure();\n\n// E até passada para uma função que exige Fn\nfn call_fn<F: Fn()>(f: F) {\n    f();\n}\ncall_fn(read_closure);
\n\n

Aqui a closure apenas lê x. Ela não precisa modificá-lo ou assumir sua propriedade. O Rust entende: “Ok, isso é seguro — damos Fn”.

\n\n

Exemplo 2: Modificação → FnMut

\n\n
let mut x = 42;\nlet mut mut_closure = || {\n    x += 1;\n    println!("x = {}", x);\n};\n\nmut_closure(); // x = 43\nmut_closure(); // x = 44\n\n// Passamos para uma função que exige FnMut\nfn call_fn_mut<F: FnMut()>(mut f: F) {\n    f();\n    f();\n}\ncall_fn_mut(mut_closure);
\n\n

Observe: declaramos mut_closure como let mut. Por quê? Porque chamar FnMut exige mutabilidade

Blogs

Book Recommendations