Programação Funcional em Rust: de A a Z
A linguagem Rust, conhecida por sua segurança de memória e alto desempenho, também oferece ferramentas poderosas para programação funcional. Diferente de linguagens puramente funcionais (Haskell), Rust permite combinar flexivelmente estilos imperativo e funcional. Neste artigo, exploraremos os conceitos-chave da programação funcional em Rust: desde closures básicas até padrões mônadas avançados. O material será útil tanto para iniciantes quanto para desenvolvedores experientes que desejam escrever código mais conciso e confiável.
1. Fundamentos: Imutabilidade e Variáveis
A programação funcional começa com a imutabilidade. Em Rust, as variáveis são imutáveis por padrão. Esta é uma diferença fundamental em relação a muitas linguagens.
// Variável imutável (padrão)let x = 10;// x = 20; // Erro de compilação!
// Variável mutável (explícita)let mut y = 5;y = 15; // FuncionaO uso de let sem mut garante que o valor não mudará após a inicialização. Isso reduz o número de efeitos colaterais e torna o código mais previsível. No estilo funcional, recomenda-se dar preferência a variáveis imutáveis, criando novos valores em vez de modificar os antigos.
2. Closures — Funções Anônimas
Closures são funções anônimas que podem capturar variáveis de seu ambiente. Em Rust, elas são implementadas através das traits Fn, FnMut e FnOnce.
2.1. Sintaxe e Captura de Variáveis
// Closure simpleslet add_one = |x: i32| -> i32 { x + 1 };println!("{}", add_one(5)); // 6
// Captura de variável do ambientelet base = 10;let add_base = |x| x + base; // base é capturado por referência (&)println!("{}", add_base(20)); // 30
// Captura com mutação (FnMut)let mut counter = 0;let mut increment = || { counter += 1; };increment();increment();println!("{}", counter); // 2
// Captura com consumo (FnOnce)let name = String::from("Rust");let consume = || { drop(name); };consume();// println!("{}", name); // Erro: name foi movido2.2. Closures como Argumentos de Funções
Closures são frequentemente passadas para funções de ordem superior, como map, filter e fold.
let numbers = vec![1, 2, 3, 4, 5];
// Usando closure com filterlet evens: Vec<i32> = numbers .into_iter() .filter(|&x| x % 2 == 0) .collect();
println!("{:?}", evens); // [2, 4]3. Iteradores e Cadeias de Transformações
Iteradores são o coração da programação funcional em Rust. Eles são preguiçosos (os cálculos ocorrem apenas ao chamar collect() ou for) e permitem construir cadeias de transformações.
3.1. Adaptadores Básicos de Iteradores
let data = vec![1, 2, 3, 4, 5, 6];
// Cadeia: filter -> map -> take -> collectlet result: Vec<i32> = data .iter() .filter(|&x| x % 2 == 0) // mantém pares .map(|x| x * 2) // dobra .take(2) // pega os dois primeiros .collect();
println!("{:?}", result); // [4, 8]3.2. Dobra (fold) e Redução
fold é o análogo de reduce em outras linguagens. Ele acumula um resultado, percorrendo todos os elementos.
let numbers = [1, 2, 3, 4, 5];
// Soma de todos os númeroslet sum = numbers.iter().fold(0, |acc, &x| acc + x);println!("Sum: {}", sum); // 15
// Fatoriallet factorial = (1..=5).fold(1, |acc, x| acc * x);println!("5! = {}", factorial); // 1203.3. Iteradores Preguiçosos Infinitos
// Iterador infinito, pegando os primeiros 10let first_ten: Vec<i32> = (0..) .filter(|x| x % 2 == 0) .map(|x| x * x)