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
A distinção entre statement e expression parece detalhe de vocabulário e decide como o código é escrito: como quase tudo em Rust produz valor, o if e o bloco {} podem estar do lado direito de um let, e o ternário deixa de fazer falta. O ponto e vírgula é o interruptor disso — presente, transforma a expression em ação e descarta o valor; ausente na última linha, faz dela o resultado. É também a causa do erro mais comum de quem começa: a função que devolve () porque sobrou um ponto e vírgula no fim.
Exercícios
Exercício 1
Explique a diferença entre statement e expression em Rust e diga por que let x = (let y = 5); não compila.
Ver resposta
✓ Resposta: Expression produz um valor; statement executa algo e não produz valor. Em Rust quase tudo é expression — if, match, blocos {} —, e por isso podem aparecer do lado direito de um let. Já let y = 5 é um statement: ele vincula um nome, mas não devolve nada. Como não há valor a atribuir, let x = (let y = 5); é recusado pelo compilador. Em C, a atribuição é uma expression e o equivalente compilaria — é justamente essa a fonte do clássico if (x = 1), que Rust torna impossível.
Exercício 2
Preveja o valor de resultado e explique o papel do ponto e vírgula:
fn main() {
let resultado = {
let a = 3;
let b = 4;
a * b
};
println!("{resultado}");
}
Ver resposta
✓ Resposta: resultado vale 12. O bloco é uma expression e seu valor é o da última linha, a * b, escrita sem ponto e vírgula. Se fosse a * b;, a linha viraria statement, o bloco passaria a não produzir valor — o tipo seria () — e o programa não compilaria, porque println! receberia um valor vazio. Em Rust o ponto e vírgula não é pontuação opcional: ele decide se algo é valor ou ação.
Exercício 3
A função abaixo não compila. Explique o erro e mostre as duas formas de corrigi-la.
fn dobro(x: i32) -> i32 {
x * 2;
}
Ver resposta
✓ Resposta: O ponto e vírgula transforma x * 2 em statement, então a função não devolve nada — o compilador acusa mismatched types: expected i32, found (). Primeira correção, removendo o ponto e vírgula para que a expression seja o valor de retorno:
fn dobro(x: i32) -> i32 {
x * 2
}
Segunda, usando return explícito:
fn dobro(x: i32) -> i32 {
return x * 2;
}
A primeira é a forma idiomática; return costuma ficar reservado para saídas antecipadas no meio da função.
Exercício 4
Escreva uma função que receba dois inteiros e devolva a soma e a diferença, e mostre como desestruturar o retorno em duas variáveis.
Ver resposta
✓ Resposta:
fn soma_e_diferenca(a: i32, b: i32) -> (i32, i32) {
(a + b, a - b)
}
fn main() {
let (soma, diferenca) = soma_e_diferenca(10, 4);
println!("soma={soma} diferenca={diferenca}");
}
O tipo de retorno é a tupla (i32, i32), e a desestruturação no let nomeia cada parte. Quando os dois valores têm significados que se confundem com facilidade, uma struct com campos nomeados costuma comunicar melhor que a tupla.
Exercício 5
Explique por que o if abaixo funciona em Rust e o que aconteceria se os dois ramos devolvessem tipos diferentes.
let categoria = if idade >= 18 { "adulto" } else { "menor" };
Ver resposta
✓ Resposta: Funciona porque if é uma expression em Rust: o valor do ramo escolhido vira o valor de toda a construção, dispensando o operador ternário que outras linguagens precisam ter. A condição é obrigatoriamente bool — não há conversão implícita de número para booleano. Se os ramos devolvessem tipos diferentes, por exemplo "adulto" e 0, o compilador recusaria com if and else have incompatible types: como o tipo de categoria precisa ser conhecido em tempo de compilação, os dois ramos têm de concordar. Um if sem else usado como valor tem o mesmo problema, porque o caminho ausente produziria ().
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
- Rust Reference — Functions — a definição formal de parâmetros, tipo de retorno e corpo como bloco — https://doc.rust-lang.org/reference/items/functions.html
- Clippy —
needless_return— o lint que acusa oreturnescrito onde bastava a expressão final — https://rust-lang.github.io/rust-clippy/master/index.html