Função como argumento em Rust: do iniciante ao profissional
\n\nVocê já escreveu em Python ou JavaScript e passou uma função para outra função com facilidade? E depois mudou para Rust e tropeçou em terreno plano? Conhecido. A pergunta "How do you pass a Rust function as a parameter?" é uma das mais populares no Stack Overflow. E não é à toa: o sistema de tipos e ownership do Rust adiciona várias camadas de complexidade, mas também abre novas possibilidades.
\n\nNeste artigo, vamos explorar três abordagens principais: ponteiros para funções (fn), closures (closures) e traits. Você aprenderá quando usar cada uma e deixará de ter medo do estilo funcional em Rust.
Por que isso é importante?
\nSaber passar funções como parâmetros não é apenas um truque acadêmico. É a base para:
\n- \n
- Criar bibliotecas flexíveis e reutilizáveis (por exemplo, iteradores, manipuladores de eventos); \n
- Implementar os padrões "Strategy" e "Observer"; \n
- Escrever código conciso e expressivo sem montes de if-else. \n
Em Rust, ao contrário de linguagens dinâmicas, o compilador verifica rigorosamente os tipos e os tempos de vida. Portanto, você precisa entender claramente o que está passando: apenas um endereço de função, uma closure com captura de contexto ou algo mais complexo.
\n\nAbordagem 1: Ponteiro para função (Function Pointer) — fn
\nA maneira mais simples e direta. Se sua função não captura variáveis do ambiente externo (ela é "pura"), você pode usar o tipo fn. Observe: fn é um tipo, enquanto Fn, FnMut, FnOnce são traits. Não confunda!
fn add_one(x: i32) -> i32 {\n x + 1\n}\n\n// Função que recebe outra função\nfn apply_twice(f: fn(i32) -> i32, value: i32) -> i32 {\n f(f(value))\n}\n\nfn main() {\n let result = apply_twice(add_one, 5);\n println!("Resultado: {}", result); // 7\n}\n\nQuando usar? Quando a função é conhecida em tempo de compilação, não captura variáveis e você não precisa da performance das closures. Ponteiros para funções são uma zero-cost abstraction, não exigem alocações.
\n\nLimitações de fn
\nA principal desvantagem: você não pode passar uma closure que captura variáveis. O código a seguir não compilará:
\n\nfn main() {\n let y = 10;\n // Closure captura y\n let closure = |x: i32| -> i32 { x + y };\n // Erro! expected fn item, found closure\n // let result = apply_twice(closure, 5);\n}\n\nPara isso, são necessários os traits Fn, FnMut, FnOnce.
Abordagem 2: Closures e os traits Fn, FnMut, FnOnce
\nRust é uma linguagem de sistema e precisa saber o que acontece com a memória. Closures podem capturar variáveis de três maneiras:
\n- \n
- Por referência (&T) — trait
Fn. Pode ser chamada quantas vezes quiser, sem modificar os dados capturados. \n - Por referência mutável (&mut T) — trait
FnMut. Pode ser chamada várias vezes, mas os dados podem ser modificados. \n - Por valor (T) — trait
FnOnce. Só pode ser chamada uma vez, após o que a closure é "consumida". \n
Exemplo com Fn
\nfn apply_twice_fn<F>(f: F, value: i32) -> i32\nwhere\n F: Fn(i32) -> i32,\n{\n f(f(value))\n}\n\nfn main() {\n let y = 10;\n let closure = |x| x + y; // captura y por referência\n let result = apply_twice_fn(closure, 5);\n println!("Fn: {}", result); // 25\n}\n\nExemplo com FnMut
\nfn apply_twice_mut<F>(mut f: F, value: i32) -> i32\nwhere\n F: FnMut(i32) -> i32,\n{\n f(f(value))\n}\n\nfn main() {\n let mut counter = 0;\n let mut closure = |x: i32| -> i32 {\n