Programação Funcional em Rust: Nível Avançado
Rust é uma linguagem multiparadigma que combina organicamente abordagens imperativas e funcionais. Para desenvolvedores já familiarizados com os fundamentos do Rust, abre-se um mundo de técnicas funcionais avançadas que permitem escrever código mais conciso, seguro e expressivo. Neste artigo, exploraremos conceitos-chave de programação funcional (FP) em nível avançado: desde o uso profundo de closures e iterators até composição de funções e emulação de monads.
1. Closures Avançadas: Propriedade, Empréstimo e Traits
Closures em Rust não são apenas funções anônimas. O compilador implementa automaticamente para elas um dos traits: Fn, FnMut ou FnOnce. Entender como uma closure captura variáveis do ambiente é crucial para escrever código eficiente.
Captura com movimentação (move closure) é usada quando a closure precisa ser proprietária dos dados capturados, por exemplo, ao ser enviada para uma nova thread:
use std::thread;
let data = vec![1, 2, 3];
let handle = thread::spawn(move || { println!("{:?}", data); // data movida para a closure});
handle.join().unwrap();Closures como argumentos de funções permitem criar APIs flexíveis. Use tipos genéricos com trait bounds:
fn apply_twice<F>(f: F, x: i32) -> i32where F: Fn(i32) -> i32,{ f(f(x))}
let square = |x| x * x;println!("{}", apply_twice(square, 2)); // Saída: 16Retornar closures é uma tarefa complexa devido ao tamanho desconhecido do tipo da closure. Use Box<dyn Fn> ou a palavra-chave impl:
fn make_adder(x: i32) -> impl Fn(i32) -> i32 { move |y| x + y}
let add_five = make_adder(5);println!("{}", add_five(10)); // 152. Iterators: Preguiça, Combinadores e Performance
Iterators em Rust são uma ferramenta funcional poderosa. Eles são preguiçosos: os cálculos ocorrem apenas quando um método terminal é chamado (ex.: collect(), for_each()).
Cadeias de combinadores permitem construir transformações complexas de dados sem loops:
let numbers = vec![1, 2, 3, 4, 5, 6];
let result: Vec<i32> = numbers .iter() .filter(|&&x| x % 2 == 0) // mantém os pares .map(|&x| x * 2) // multiplica por 2 .take(2) // pega os dois primeiros .collect();
println!("{:?}", result); // [4, 8]Iterators personalizados são criados implementando o trait Iterator. Por exemplo, um iterator de números de Fibonacci:
struct Fibonacci { curr: u64, next: u64,}
impl Fibonacci { fn new() -> Self { Fibonacci { curr: 0, next: 1 } }}
impl Iterator for Fibonacci { type Item = u64;
fn next(&mut self) -> Option<Self::Item> { let new_next = self.curr.checked_add(self.next)?; self.curr = self.next; self.next = new_next; Some(self.curr) }}
let fib: Vec<u64> = Fibonacci::new().take(10).collect();println!("{:?}", fib); // [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]Performance dos iterators frequentemente não fica atrás dos loops manuais e, em alguns casos, o compilador LLVM gera código mais otimizado.
3. Composição de Funções e Estilo Monádico
Rust não possui composição de funções embutida, mas é fácil implementá-la. A composição permite construir novas funções a partir de funções existentes.
Composição simples com closures:
fn compose<F, G, A, B, C>(f: F, g: G) -> impl Fn(A) -> Cwhere F: Fn(B) -> C, G: Fn(A) -> B,{ move |x| f(g(x))}
let add_o
```