Funções em Rust: Sintaxe, Parâmetros e Retornos
Funções são a base da organização de código em Rust. Diferente de muitas linguagens, Rust distingue claramente entre instruções (que não retornam valor) e expressões (que retornam). Uma função é declarada com a palavra-chave fn, seguida de um nome, parâmetros entre parênteses e, opcionalmente, um tipo de retorno precedido por ->.
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn greet(name: &str) {
println!("Olá, {}!", name);
}
fn main() {
let result = add(5, 3);
println!("Resultado: {}", result);
greet("Maria");
}
Note que em Rust, a última linha sem ; é uma expressão que retorna seu valor. Se adicionar ;, torna-se uma instrução que retorna () (unit type). Os parâmetros exigem anotação de tipo — isso é obrigatório e não há tipo padrão inferido. Funções podem retornar múltiplos valores usando tuplas, e o compilador força a tratativa correta de tipos, eliminando bugs em tempo de compilação.
Closures e Funções Anônimas
Closures são funções sem nome que capturam variáveis do escopo envolvente. São poderosas para programação funcional e callbacks. A sintaxe é |parametros| { corpo }, onde os tipos podem ser inferidos ou explícitos.
fn main() {
let num = 5;
let add_num = |x| x + num; // Captura 'num' por referência
println!("{}", add_num(3)); // Imprime 8
let multiply = |a: i32, b: i32| -> i32 { a * b };
println!("{}", multiply(4, 2)); // Imprime 8
let values = vec![1, 2, 3, 4];
let doubled: Vec<i32> = values.iter().map(|x| x * 2).collect();
println!("{:?}", doubled); // [2, 4, 6, 8]
}
Closures implementam os traits Fn, FnMut ou FnOnce, dependendo de como capturam variáveis. Fn captura por referência imutável, FnMut por referência mutável, e FnOnce consome a variável. O compilador infere automaticamente qual trait usar.
Expressões: O Coração de Rust
Em Rust, quase tudo é uma expressão. Blocos {}, loops, condicionais — todos retornam valores. Isso diferencia Rust de linguagens como C ou Java, onde essas estruturas são apenas instruções. Uma expressão não termina com ponto-e-vírgula; se terminar, vira uma instrução.
fn main() {
// Expressão if
let number = 5;
let result = if number > 0 {
"positivo"
} else {
"não positivo"
};
println!("{}", result); // "positivo"
// Expressão match
let value = 42;
let message = match value {
0 => "zero",
1..=10 => "pequeno",
_ => "grande",
};
println!("{}", message); // "grande"
// Bloco como expressão
let x = {
let y = 5;
let z = 6;
y + z // Sem ; retorna o valor
};
println!("{}", x); // 11
}
Esse design força o programador a pensar em fluxo de dados de forma mais explícita. A ausência de um ; é sintaticamente significativa e intencional, evitando retornos acidentais de ().
O Sistema de Tipos: Estático, Forte e Inteligente
Rust possui um sistema de tipos estático e forte, onde cada valor tem um tipo conhecido em tempo de compilação. O sistema é tão rigoroso que evita classes inteiras de bugs. Além dos tipos primitivos (i32, f64, bool, &str), Rust oferece tipos compostos, genéricos e traits para abstraçãoe reutilização.
Tipos Genéricos e Traits
Genéricos permitem escrever código reutilizável sem sacrificar segurança de tipo. Traits definem comportamentos compartilhados entre tipos diferentes, funcionando como interfaces.
// Função genérica
fn print_it<T: std::fmt::Display>(val: T) {
println!("Valor: {}", val);
}
// Trait customizado
trait Animal {
fn fazer_som(&self) -> &str;
}
struct Cachorro;
struct Gato;
impl Animal for Cachorro {
fn fazer_som(&self) -> &str {
"Au au!"
}
}
impl Animal for Gato {
fn fazer_som(&self) -> &str {
"Miau!"
}
}
fn main() {
print_it(42);
print_it("Olá");
let dog = Cachorro;
let cat = Gato;
println!("{}", dog.fazer_som()); // Au au!
println!("{}", cat.fazer_som()); // Miau!
}
O bound T: std::fmt::Display garante que T implementa o trait Display. Isso permite que o compilador verifique segurança sem runtime overhead. Traits também viabilizam polimorfismo sem herança, seguindo composição sobre herança.
Option e Result: Tratamento de Erros Seguro
Rust não possui null; ao invés, usa Option<T> para valores opcionais e Result<T, E> para operações que podem falhar. Isso força o tratamento explícito de casos de erro.
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("Divisão por zero!".to_string())
} else {
Ok(a / b)
}
}
fn main() {
match divide(10, 2) {
Ok(resultado) => println!("Resultado: {}", resultado),
Err(erro) => println!("Erro: {}", erro),
}
// Sintaxe '?' para propagação de erro
let result = divide(20, 5).unwrap_or(0);
println!("{}", result); // 4
}
O operador ? propaga erros automaticamente, reduzindo boilerplate. unwrap() extrai o valor ou panics se for erro; use apenas quando tiver certeza. Essa abordagem torna tratamento de erro explícito e impossível de ignorar.
Conclusão
Dominando funções, expressões e o sistema de tipos de Rust, você terá acesso à filosofia central da linguagem: segurança em tempo de compilação sem sacrificar performance. Primeira lição: funções e closures são ferramentas poderosas para composição de código. Segunda: expressões, não instruções, levam a um código mais funcional e previsível. Terceira: o sistema de tipos não é um obstáculo — é seu aliado contra bugs, forçando você a pensar claramente sobre possibilidades de erro e fluxo de dados.