Introdução à programação funcional em Rust
Rust é uma linguagem multiparadigma que, juntamente com os estilos imperativo e orientado a objetos, suporta ativamente a programação funcional. Recursos integrados, como closures, iteradores, um sistema de tipos poderoso e imutabilidade por padrão, permitem escrever código conciso, seguro e expressivo. Neste artigo, vamos explorar os conceitos-chave da abordagem funcional em Rust e mostrar como aplicá-los na prática.
Fundamentos da abordagem funcional: imutabilidade e funções puras
A programação funcional é construída sobre dois pilares: imutabilidade de dados e funções puras. Rust incentiva a imutabilidade — por padrão, todas as variáveis são imutáveis. Para alterar um valor, é necessário especificar explicitamente mut.
fn main() { let x = 5; // variável imutável // x = 6; // erro de compilação let mut y = 10; y += 5; // permitido, pois y é mutável println!("x = {}, y = {}", x, y);}Uma função pura não tem efeitos colaterais e sempre retorna o mesmo resultado para os mesmos argumentos. Rust permite escrever tais funções, mas não obriga a isso — você pode misturar estilos livremente.
fn add(a: i32, b: i32) -> i32 { a + b // função pura}Closures — funções anônimas
Closures em Rust são funções anônimas que podem capturar variáveis do escopo circundante. Elas são definidas com a sintaxe |parâmetros| corpo. As closures são de três tipos, dependendo da forma de captura: por referência (&T), por referência mutável (&mut T) e por valor (T).
Exemplo simples de closure
fn main() { let factor = 2; let multiply = |x: i32| x * factor; println!("{}", multiply(10)); // 20}Closure com captura mutável
fn main() { let mut counter = 0; let mut increment = || { counter += 1; counter }; println!("{}", increment()); // 1 println!("{}", increment()); // 2}Closures são amplamente utilizadas com iteradores e para passar comportamentos para funções de ordem superior.
Iteradores e cadeias funcionais
Iteradores em Rust são uma das abstrações funcionais mais poderosas. Em vez de loops, você pode construir cadeias de métodos como map, filter, fold e outros. Os iteradores são preguiçosos — os cálculos ocorrem apenas quando um consumidor é chamado (por exemplo, collect ou for_each).
Exemplo: filtragem e transformação
fn main() { 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 * x) // eleva ao quadrado .collect(); println!("{:?}", result); // [4, 16, 36]}Fold — acumulação de valores
fn main() { let numbers = vec![1, 2, 3, 4]; let sum = numbers.iter().fold(0, |acc, x| acc + x); println!("Soma: {}", sum); // 10}Iteradores tornam o código declarativo: você descreve o que precisa ser feito, não como. Isso reduz a probabilidade de erros e melhora a legibilidade.
Pattern matching
A construção match é um elemento central do estilo funcional em Rust. Ela permite desestruturar estruturas de dados complexas, lidar com variantes de Option e Result e escrever verificações exaustivas.
Exemplo básico
fn describe_number(n: i32) -> &'static str { match n { 0 => "zero", 1 | 2 => "número pequeno", 3..=10 => "número médio", _ if n < 0 => "negativo", _ => "número grande" }}