Programação Funcional em Rust: De Iteradores a Mônadas
Rust é uma linguagem multiparadigma que combina perfeitamente estilos imperativos, orientados a objetos e funcionais. Apesar de Rust não ser uma linguagem puramente funcional (como Haskell), seu sistema de tipos e modelo de propriedade permitem escrever código elegante, seguro e expressivo em estilo funcional. Neste artigo, vamos detalhar os conceitos-chave da programação funcional (PF) em Rust: closures, iteradores, pattern matching e trabalho com Option e Result. Este material será útil tanto para iniciantes quanto para desenvolvedores experientes que desejam aprofundar sua compreensão das capacidades funcionais do Rust.
Closures e Expressões Lambda
Closures em Rust são funções anônimas que podem capturar variáveis do escopo ao redor. Elas são a base da abordagem funcional, permitindo que comportamentos sejam passados como dados.
A sintaxe para closures é concisa: |parâmetros| corpo. Closures podem ser passadas para funções, armazenadas em variáveis e retornadas de funções. É importante lembrar das três traits que controlam a captura: Fn (empréstimo), FnMut (empréstimo mutável) e FnOnce (consumo).
fn main() { // Closure simples let add_one = |x: i32| x + 1; println!("{}", add_one(5)); // 6
// Capturando uma variável let factor = 10; let multiply = |x| x * factor; println!("{}", multiply(3)); // 30
// Closure com trait FnOnce let name = String::from("Rust"); let greet = move || { println!("Olá, {}!", name); // name é movido }; greet(); // println!("{}", name); // Erro! name não está mais disponível}A palavra-chave move força a captura por valor (movimento), o que é frequentemente necessário ao trabalhar com multithreading ou ao passar uma closure para outro contexto.
Iteradores: Processamento Preguiçoso e Encadeamento de Métodos
Um dos aspectos mais fortes do estilo funcional em Rust é o poderoso adaptador de iteradores. Em vez de loops for explícitos e mutação de variáveis, você usa encadeamento de métodos: map, filter, fold, collect e outros. Iteradores em Rust são preguiçosos — eles não realizam computações até que um método consumidor seja chamado (ex.: collect() ou for_each()).
fn main() { let numbers = vec![1, 2, 3, 4, 5, 6];
// Abordagem funcional: filtragem e transformação let result: Vec<i32> = numbers .iter() // cria um iterador .filter(|&x| x % 2 == 0) // mantém apenas números pares .map(|x| x * x) // eleva ao quadrado .collect(); // coleta em um vetor
println!("{:?}", result); // [4, 16, 36]
// Soma usando fold (redução) let sum = numbers.iter().fold(0, |acc, x| acc + x); println!("Soma: {}", sum); // 21
// Verificação de condição com any e all let has_even = numbers.iter().any(|x| x % 2 == 0); println!("Tem números pares: {}", has_even); // true}Usar iteradores torna o código declarativo, legível e menos propenso a erros relacionados a indexação e limites de array. O compilador Rust frequentemente otimiza cadeias de iteradores em código de máquina eficiente, comparável a loops manuais.
Pattern Matching e Desestruturação
Pattern matching (match) é o coração do controle de fluxo funcional em Rust. Ele permite tratar com segurança todas as variantes possíveis de dados, especialmente em combinação com tipos de dados algébricos (enum). Padrões podem ser aninhados, incluir guards e desestruturar structs, tuplas e enums.
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32),}
fn handle_