Funções: parâmetros, retorno e expressões vs statements
1. Introdução às Funções em Rust
Em Rust, funções são blocos de código reutilizáveis que realizam tarefas específicas. A sintaxe básica de declaração utiliza a palavra-chave fn, seguida pelo nome da função em snake_case (convenção obrigatória), parênteses para parâmetros e um bloco de código entre chaves.
fn saudacao() {
println!("Olá, Rust!");
}
fn main() {
saudacao();
}
O ponto de entrada de todo programa Rust é a função fn main(), que não possui parâmetros e retorna implicitamente uma tupla vazia ().
2. Parâmetros de Função
Em Rust, todos os parâmetros de função são obrigatórios e exigem anotação explícita de tipo. Diferente de linguagens como JavaScript ou Python, não existem parâmetros opcionais ou valores padrão nativos.
fn soma(a: i32, b: i32) {
println!("A soma é: {}", a + b);
}
fn main() {
soma(10, 20);
}
Múltiplos parâmetros são separados por vírgula. A passagem pode ser por valor (transferindo ownership) ou por referência (emprestando o valor), conceito que exploraremos mais adiante.
fn exibe_nome(nome: &String) {
println!("Nome: {}", nome);
}
3. Retorno de Valores
Funções em Rust podem retornar valores utilizando a sintaxe -> Tipo. Existem duas formas principais de retorno:
Retorno implícito — a última expressão do bloco, sem ponto e vírgula, é automaticamente retornada:
fn quadrado(x: i32) -> i32 {
x * x // sem ponto e vírgula
}
fn main() {
let resultado = quadrado(5);
println!("5 ao quadrado é: {}", resultado);
}
Retorno explícito — utilizando a palavra-chave return, útil para saídas antecipadas:
fn maior(a: i32, b: i32) -> i32 {
if a > b {
return a;
}
b
}
Para retornar múltiplos valores, use tuplas:
fn dividir(dividendo: i32, divisor: i32) -> (i32, i32) {
(dividendo / divisor, dividendo % divisor)
}
fn main() {
let (quociente, resto) = dividir(17, 5);
println!("Quociente: {}, Resto: {}", quociente, resto);
}
4. Expressões vs Statements — A Distinção Central
Esta é a distinção mais importante para entender funções em Rust. Tudo em Rust é classificado como:
- Expressão: produz um valor e pode ser composta por outras expressões.
- Statement: executa uma ação, mas não retorna valor.
fn main() {
// Statement: declaração de variável
let x = 5;
// Expressão: 5 + 5 produz o valor 10
let y = 5 + 5;
// Statement: chamada de função
println!("x = {}", x);
}
O ponto e vírgula é o divisor de águas: ele converte uma expressão em um statement. Sem ponto e vírgula, o código é uma expressão que retorna valor; com ponto e vírgula, vira um statement que não retorna nada.
fn main() {
// Bloco como expressão
let valor = {
let x = 3;
x + 1 // expressão, retorna 4
};
println!("Valor: {}", valor); // 4
// Bloco como statement
{
let x = 3;
x + 1; // statement, não retorna nada
};
}
5. Corpo de Função como Expressão
O corpo de uma função é um bloco { }, que funciona como uma expressão. O último valor do bloco (sem ponto e vírgula) é retornado automaticamente.
fn classificar_nota(nota: u8) -> &'static str {
if nota >= 90 {
"A"
} else if nota >= 80 {
"B"
} else {
"C"
}
}
if e match também são expressões em Rust, permitindo atribuições condicionais elegantes:
fn main() {
let numero = 7;
let par_ou_impar = if numero % 2 == 0 { "par" } else { "ímpar" };
println!("{} é {}", numero, par_ou_impar);
let descricao = match numero {
1 => "um",
2 => "dois",
_ => "outro",
};
}
6. Funções com Parâmetros de Referência e Mutabilidade
Rust possui regras estritas de ownership. Para evitar a transferência de propriedade, usamos referências:
Referência imutável (&T) — permite ler o valor sem modificar:
fn calcular_tamanho(s: &String) -> usize {
s.len()
}
fn main() {
let texto = String::from("Rust");
let tamanho = calcular_tamanho(&texto);
println!("'{}' tem {} caracteres", texto, tamanho); // texto ainda é válido
}
Referência mutável (&mut T) — permite modificar o valor emprestado:
fn adicionar_ponto(s: &mut String) {
s.push_str("!");
}
fn main() {
let mut saudacao = String::from("Olá");
adicionar_ponto(&mut saudacao);
println!("{}", saudacao); // "Olá!"
}
Regra fundamental: você pode ter várias referências imutáveis ou uma única referência mutável em um mesmo escopo, nunca ambas simultaneamente.
7. Funções Aninhadas e Escopo
Rust permite declarar funções dentro de funções, mas com limitações importantes:
fn externa() {
fn interna() {
println!("Dentro da função interna");
}
interna();
}
fn main() {
externa();
// interna(); // ERRO! não está no escopo
}
Funções aninhadas não capturam variáveis do escopo externo. Para isso, utilizamos closures:
fn main() {
let fator = 2;
let multiplicador = |x: i32| x * fator; // closure captura 'fator'
println!("{}", multiplicador(5)); // 10
}
8. Boas Práticas e Erros Comuns
Erro 1: Esquecer ponto e vírgula no retorno implícito
fn erro() -> i32 {
42; // Erro: retorna (), não i32
}
Erro 2: Confundir () com ausência de retorno
Toda função que não especifica retorno retorna () (tupla vazia). Isso é diferente de "não retornar nada".
fn ok() {} // retorna ()
fn explicito() -> () {} // equivalente
Erro 3: Uso excessivo de return
Prefira retorno implícito para funções curtas:
// Ruim
fn soma(a: i32, b: i32) -> i32 {
return a + b;
}
// Bom
fn soma(a: i32, b: i32) -> i32 {
a + b
}
Dicas de estilo:
- Use rustfmt para formatação consistente
- Prefira funções pequenas e focadas
- Documente com /// para gerar documentação automática
/// Calcula a área de um retângulo
fn area_retangulo(largura: u32, altura: u32) -> u32 {
largura * altura
}
Conclusão
Dominar funções em Rust requer compreender a diferença fundamental entre expressões e statements, saber quando usar retorno implícito vs explícito, e entender como parâmetros interagem com o sistema de ownership. Esses conceitos formam a base para escrever código idiomático e seguro em Rust.
Referências
- The Rust Programming Language — Functions — Capítulo oficial sobre funções, parâmetros e retorno
- Rust by Example — Functions — Exemplos práticos de declaração e uso de funções
- Rust Reference — Expressions — Documentação detalhada sobre expressões e sua avaliação
- Rust RFC 0092 — Struct Update Syntax — Discussão sobre expressões vs statements no design da linguagem
- The Rustonomicon — Ownership — Guia avançado sobre ownership, referências e borrowing
- Rust by Example — Closures — Diferenças entre funções aninhadas e closures
- Effective Rust — Functions — Boas práticas e padrões para funções em Rust