Funções, Expressões e Como Rust Pensa Diferente sobre Retorno de Valores
Se você vem de Python, JavaScript ou Java, já sabe o que é uma função. Mas Rust tem uma visão particular sobre funções que vai te surpreender — especialmente a distinção entre statements e expressions, e a forma como valores são retornados. É sutil no começo, mas muda profundamente a forma como você escreve código.
Declarando funções
Funções em Rust são declaradas com fn, seguido do nome, parâmetros e corpo entre chaves. Por convenção, nomes de funções usam snake_case:
fn saudacao() {
println!("Olá, mundo do Rust!");
}
fn main() {
saudacao();
}
Diferente de Python ou JavaScript, a ordem de declaração das funções não importa em Rust. Você pode chamar uma função antes de declará-la no arquivo — o compilador encontra tudo antes de gerar o código.
Parâmetros e tipos obrigatórios
Em Rust, toda função deve declarar explicitamente o tipo de cada parâmetro. Não há inferência aqui — essa é uma decisão deliberada. Funções são contratos, e contratos precisam ser explícitos:
fn somar(a: i32, b: i32) {
println!("{a} + {b} = {}", a + b);
}
fn main() {
somar(10, 32);
}
Múltiplos parâmetros são separados por vírgula, cada um com sua própria anotação de tipo. Não existe sintaxe como a, b: i32 para compartilhar o tipo — cada parâmetro é declarado individualmente. Isso torna a assinatura mais verbosa, mas infinitamente mais legível.
Statements vs. Expressions — a distinção que muda tudo
Aqui começa a parte que mais confunde quem vem de outras linguagens.
Em Rust, há uma diferença fundamental entre:
- Statement — uma instrução que executa uma ação e não retorna valor
- Expression — um trecho de código que produz um valor
Em linguagens como C ou Java, essa distinção existe mas é informal. Em Rust, ela é parte da gramática da linguagem.
Veja o exemplo:
fn main() {
let x = 5; // statement: não produz valor
let y = {
let a = 3;
a + 2 // expression: produz 5 (sem ponto e vírgula!)
};
println!("y = {y}"); // y = 5
}
O bloco { let a = 3; a + 2 } é uma expression. Ele avalia para 5, que é atribuído a y. A linha a + 2 não tem ponto e vírgula — e isso é crucial. Adicionar ; ao final de uma expression a transforma em um statement, e ela passa a retornar () — o tipo unitário vazio de Rust.
Esse detalhe de ponto e vírgula é responsável por uma boa parte da confusão inicial de quem aprende Rust. Guarde bem: ausência de ponto e vírgula na última linha de um bloco significa retorno implícito daquele valor.
Funções com retorno de valor
Para declarar que uma função retorna um valor, use -> seguido do tipo após os parênteses:
fn quadrado(n: i32) -> i32 {
n * n // sem ponto e vírgula: retorno implícito
}
fn main() {
let resultado = quadrado(7);
println!("7² = {resultado}"); // 49
}
Não há palavra-chave return aqui. A última expressão do corpo da função é automaticamente o valor retornado. Esse estilo é idiomático em Rust e você vai vê-lo em praticamente todo código da linguagem.
O return explícito existe, mas é reservado para retornos antecipados — quando você quer sair da função antes de chegar ao final:
fn dividir(a: f64, b: f64) -> f64 {
if b == 0.0 {
return 0.0; // saída antecipada
}
a / b // retorno normal ao final
}
fn main() {
println!("{}", dividir(10.0, 2.0)); // 5
println!("{}", dividir(10.0, 0.0)); // 0
}
Funções que não retornam valor
Quando uma função não retorna nada significativo, seu tipo de retorno é () — pronunciado "unit". Você raramente escreve isso explicitamente, pois é implícito quando não há ->:
fn imprimir_linha(texto: &str) {
println!("{texto}");
// retorna () implicitamente
}
É equivalente a:
fn imprimir_linha(texto: &str) -> () {
println!("{texto}");
}
A segunda forma existe mas é considerada redundante. Evite-a.
Funções como expressões
Como tudo em Rust tende a ser uma expression, chamadas de função também produzem valores e podem ser usadas diretamente:
fn celsius_para_fahrenheit(c: f64) -> f64 {
c * 1.8 + 32.0
}
fn main() {
let temperaturas = [0.0, 20.0, 37.0, 100.0];
for temp in temperaturas {
println!("{temp}°C = {:.1}°F", celsius_para_fahrenheit(temp));
}
}
Saída:
0°C = 32.0°F
20°C = 68.0°F
37°C = 98.6°F
100°C = 212.0°F
Múltiplos valores de retorno com tuplas
Rust não suporta múltiplos valores de retorno nativamente — mas tuplas resolvem isso com elegância:
fn minmax(lista: &[i32]) -> (i32, i32) {
let mut min = lista[0];
let mut max = lista[0];
for &valor in lista {
if valor < min { min = valor; }
if valor > max { max = valor; }
}
(min, max) // retorna a tupla
}
fn main() {
let numeros = [3, 7, 1, 9, 4, 6, 2, 8, 5];
let (minimo, maximo) = minmax(&numeros);
println!("Mínimo: {minimo}, Máximo: {maximo}");
}
Saída:
Mínimo: 1, Máximo: 9
Note o &[i32] — isso é um slice, uma referência a uma sequência de inteiros. Não se preocupe com os detalhes agora; vamos explorar referências e slices em profundidade nos próximos artigos.
Funções aninhadas
Em Rust, você pode declarar funções dentro de outras funções. Isso é útil para organizar lógica auxiliar que só faz sentido localmente:
fn calcular_imc(peso: f64, altura: f64) -> f64 {
fn imc(p: f64, h: f64) -> f64 {
p / (h * h)
}
fn classificar(imc: f64) -> &'static str {
if imc < 18.5 { "Abaixo do peso" }
else if imc < 25.0 { "Peso normal" }
else if imc < 30.0 { "Sobrepeso" }
else { "Obesidade" }
}
let valor = imc(peso, altura);
println!("IMC: {valor:.2} — {}", classificar(valor));
valor
}
fn main() {
calcular_imc(70.0, 1.75);
calcular_imc(50.0, 1.75);
calcular_imc(100.0, 1.75);
}
Saída:
IMC: 22.86 — Peso normal
IMC: 16.33 — Abaixo do peso
IMC: 32.65 — Obesidade
Um padrão que você vai ver muito
Em Rust idiomático, é muito comum que funções terminem com uma expressão que resume seu propósito. Aprenda a reconhecer esse padrão:
fn area_retangulo(largura: f64, altura: f64) -> f64 {
largura * altura
}
fn perimetro_retangulo(largura: f64, altura: f64) -> f64 {
2.0 * (largura + altura)
}
fn descrever_retangulo(largura: f64, altura: f64) {
println!(
"Retângulo {}×{}: área={:.2}, perímetro={:.2}",
largura,
altura,
area_retangulo(largura, altura),
perimetro_retangulo(largura, altura)
);
}
fn main() {
descrever_retangulo(5.0, 3.0);
descrever_retangulo(10.0, 2.5);
}
Saída:
Retângulo 5×3: área=15.00, perímetro=16.00
Retângulo 10×2.5: área=25.00, perímetro=25.00
O que vem a seguir
Hoje entendemos a anatomia de funções em Rust e a distinção crucial entre statements e expressions. Essa distinção vai aparecer de novo quando estudarmos estruturas de controle — e vai fazer muito mais sentido agora.
No próximo artigo, vamos explorar controle de fluxo: if, loop, while e for. E você vai descobrir que em Rust, um if não é apenas uma instrução — é uma expression que produz valor. O que significa que você pode escrever coisas como let x = if condicao { 1 } else { 2 }; — elegante, seguro e completamente idiomático.
Fontes e leituras recomendadas
- The Rust Programming Language, Cap. 3.3 — Functions — https://doc.rust-lang.org/book/ch03-03-how-functions-work.html
- Rust by Example — Functions — https://doc.rust-lang.org/rust-by-example/fn.html
- Rust Reference — Expressions — https://doc.rust-lang.org/reference/expressions.html
- Rust Reference — Statements — https://doc.rust-lang.org/reference/statements.html
- Rustlings, exercícios de funções — seção
functions— https://github.com/rust-lang/rustlings