Callbacks em Rust: da dor à elegância através de expressões idiomáticas

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

Introdução: por que callbacks em Rust são um desafio (e uma oportunidade)

\n

Se você já escreveu em JavaScript, Python ou C#, os callbacks são uma ferramenta familiar para você. Você passa uma função que será chamada mais tarde e vive tranquilo. Mas quando você chega ao Rust, esse padrão simples de repente se transforma em um campo minado: propriedade, empréstimo, tempos de vida, dyn vs impl...

\n

No Stack Overflow, a pergunta "Idiomatic callbacks in Rust" acumulou milhares de visualizações. Isso significa que até desenvolvedores experientes tropeçam neste tópico. Mas a boa notícia: o Rust oferece não uma, mas várias maneiras elegantes de implementar callbacks. E hoje vamos analisar todas elas — desde closures simples até poderosos trait objects e padrões enum.

\n

Este artigo é para você, se você:

\n
    \n
  • Começou a estudar Rust e quer entender como organizar corretamente arquiteturas assíncronas e orientadas a eventos;
  • \n
  • Está migrando de outras linguagens e procurando análogos idiomáticos para padrões familiares;
  • \n
  • Quer escrever código Rust limpo, seguro e eficiente.
  • \n
\n

Vamos nessa!

\n\n

O problema: o que há de errado com callbacks simples?

\n

Imagine que estamos escrevendo um agendador de tarefas simples. Em Python, seria assim:

\n
def schedule(callback):\n    # faz algo\n    callback(result)
\n

Em Rust, a primeira tentativa pode ser:

\n
fn schedule(callback: fn(i32) -> i32) {\n    let result = 42;\n    callback(result);\n}
\n

E isso funciona! Mas apenas para funções, não para closures. Tente passar uma closure que captura uma variável:

\n
let factor = 2;\nlet closure = |x| x * factor;\nschedule(closure); // Erro! expected fn pointer, found closure
\n

O compilador reclama com razão: fn é um ponteiro para função, enquanto uma closure é um tipo anônimo que pode capturar o ambiente. Precisamos de uma abordagem mais flexível.

\n\n

Abordagem 1: Closures com impl Fn (simplicidade e flexibilidade)

\n

A maneira mais simples e frequentemente usada é usar um tipo genérico com o trait Fn, FnMut ou FnOnce.

\n

Quando usar Fn, FnMut, FnOnce?

\n
    \n
  • FnOnce — se o callback for chamado exatamente uma vez (consome os valores capturados);
  • \n
  • FnMut — se o callback puder modificar o estado capturado (chamado várias vezes);
  • \n
  • Fn — se o callback não modificar o estado (função pura).
  • \n
\n

Exemplo com impl Fn:

\n
fn schedule<F>(callback: F)\nwhere\n    F: Fn(i32) -> i32,\n{\n    let result = 42;\n    println!("Resultado: {}", callback(result));\n}\n\nfn main() {\n    let factor = 2;\n    let closure = |x| x * factor;\n    schedule(closure); // Funciona!\n    \n    // Também pode passar uma função comum\n    fn double(x: i32) -> i32 { x * 2 }\n    schedule(double);\n}
\n

Vantagens: extremamente flexível, suporta closures, seguro, legível.
\nDesvantagens: monomorfização — para cada tipo de closure, o compilador gera uma versão separada da função. Se houver muitos callbacks, o código pode inchar.

\n\n

Abordagem 2: Trait objects Box<dyn Fn> (despacho dinâmico)

\n

Se você quiser armazenar callbacks em coleções (por exemplo, em um Vec) ou passá-los entre threads, os genéricos não funcionarão — é necessário um único tipo. Os trait objects vêm em socorro:

\n
type Callback = Box<dyn Fn(i32) -> i32>;\n\nstruct Scheduler {\n    callbacks: Vec<Callback>,\n}\n\nimpl Scheduler {\n    fn new() -> Self {\n        Scheduler { callbacks: Vec::new() }\n    }\n    \n    fn register(&mut self, callback                

Blogs

Book Recommendations