Rust: Funções e escopo em Rust
Última atualização: 2026-08-26
As funções são cidadãos de primeira classe no Rust — seus tipos de parâmetros e de retorno funcionam como a primeira linha de defesa do compilador.
As funções são os blocos básicos de construção do código. A sintaxe das funções no Rust é semelhante à das linguagens da família C, mas apresenta um estilo único no que diz respeito à correspondência de padrões de argumentos e aos valores de retorno das expressões.
1. O que você vai aprender
- Use
fnpara definir uma função e especificar os tipos dos parâmetros e do valor de retorno - Três formas de passar parâmetros: passagem por valor, passagem por referência e referência mutável
- Valores de retorno de funções e retorno antecipado
- Âmbito do bloco e sombreamento de variáveis
- Funcionam como as unidades básicas de organização do código
- Uma introdução a ponteiros de função e funções de ordem superior
2. A história de uma cafeteria
(1) O desafio: não existe um processo padronizado para preparar café
Lisa abriu uma cafeteria, e os negócios vêm indo cada vez melhor, mas também surgiram alguns problemas:
- Cada barista prepara o latte de uma maneira diferente
- Alguns colocam o leite primeiro e depois o café; outros colocam o café primeiro e depois o leite.
- Um cliente reclamou: “O mesmo café com leite tem um gosto diferente a cada vez”.
- Lisa quer escrever uma receita padronizada — exatamente como uma função: basta inserir as quantidades de leite e café, e ela produz um café com leite com um sabor consistente.
“Seria ótimo se preparar café fosse tão consistente quanto chamar uma função: a mesma entrada sempre produz o mesmo resultado.”
(2) Abordagem para funções em Rust
// How to Make a Latte"Function":Input Parameters,Brew Coffee
fn make_latte(milk_ml: i32, coffee_ml: i32) -> String {
let result = format!("Latte Complete:{}ml Milk + {}ml Coffee,Fragrant and Delicious!", milk_ml, coffee_ml);
result // No semicolons——Expression Return Value
}
fn main() {
let my_coffee = make_latte(200, 30);
println!("{}", my_coffee);
let another = make_latte(150, 45);
println!("{}", another);
}
As funções são como receitas de café: os parâmetros são os ingredientes (quantidade de leite, quantidade de café) e o valor de retorno é o produto final. A mesma entrada garante a mesma saída — esse é o determinismo das funções.
3. Definições de funções
(1) Sintaxe básica
fn function_name(param1: Type1, param2: Type2) -> ReturnType {
// Function Body
return_value // Expression returns(No semicolons)
}
graph TB
A[fn Function Name] --> B[(List of Parameters)]
A --> C[-> Return Type]
A --> D[{ Function Body }]
B --> E[Each parameter: name: Type]
D --> F[Final expression = Return Value]
D --> G[return Keywords = Return Early]
(2) Várias formas de parâmetros e valores de retorno
| Forma | Sintaxe | Exemplo |
|---|---|---|
| Sem parâmetros, sem valor de retorno | fn foo() |
fn greet() { println!("hi"); } |
| Aceita parâmetros, não retorna nada | fn foo(x: i32) |
fn show(n: i32) { println!("{}", n); } |
| Com parâmetros e um valor de retorno | fn foo(x: i32) -> i32 |
fn double(x: i32) -> i32 { x * 2 } |
| Vários valores de retorno (tupla) | fn foo() -> (i32, bool) |
fn stats() -> (i32, bool) { (42, true) } |
| Voltar | return value; |
if x < 0 { return 0; } |
Regra básica: A última expressão no corpo da função é o valor de retorno (sem ponto-e-vírgula). Use a palavra-chave
returnpara um retorno antecipado.
(3) Métodos de passagem de parâmetros
| Método de passagem | Sintaxe | Mudança de propriedade | Cenários aplicáveis |
|---|---|---|---|
| Passagem por valor | fn foo(s: String) |
A propriedade é transferida para a função | A função precisa utilizar o valor |
| Passagem por referência imutável | fn foo(s: &String) |
Empréstimo, a propriedade permanece inalterada | Somente leitura |
| Passagem por referência | fn foo(s: &mut String) |
Empréstimo, a propriedade permanece inalterada | Os dados precisam ser modificados |
(4) Comparação dos valores de retorno
| Método | Sintaxe | Mudança de propriedade | Exemplo |
|---|---|---|---|
| Retornos de expressão | Sem ponto-e-vírgula no final da linha | Transferência de propriedade | fn f() -> String { s } |
| Voltar | return value; |
Transferir propriedade | if err { return None; } |
| Retornos com tuplas múltiplas | -> (T1, T2) |
Transferências múltiplas de propriedade | fn f() -> (i32, bool) |
| Parâmetro de saída | &mut T |
Sem transferência de propriedade | fn fill(buf: &mut Vec<i32>) |
4. Âmbito e sombreamento
(1) Âmbito do bloco
O escopo no Rust é definido por {}. Cada bloco possui seu próprio escopo:
fn main() {
let x = 10; // Outer Scope
{
let y = 20; // Inner Scope,y Available only here
println!("Inside: x={}, y={}", x, y); // ✅ Can access the outer layer x
}
// println!("{}", y); // ❌ Compilation Error:y Not in this scope
println!("External: x={}", x); // ✅
}
(2) Ocultação de variáveis
Variáveis externas podem ser sobrescritas por variáveis internas com o mesmo nome, e o código interno só pode acessar as variáveis internas:
fn main() {
let x = 1; // Outer layer x = 1
{
let x = 2; // Inner layer x Covered the outer layer x
println!("Inside x = {}", x); // Output: Inside x = 2
}
println!("External x = {}", x); // Output: External x = 1
}
(3) Âmbito dos parâmetros de função
Os parâmetros da função são visíveis em todo o corpo da função:
fn foo(x: i32) {
// x Available throughout the entire function body
let y = x + 1;
println!("x={}, y={}", x, y);
} // End of function, x and y are both destroyed
5. Exemplos de funções
▶ Exemplo 1: Várias assinaturas de função (Dificuldade ⭐)
// ============================================
// Functions with Different Combinations of Parameters and Return Types
// ============================================
// No parameters, no return value
fn print_separator() {
println!("-------------------");
}
// Single-parameter functions with return values
fn square(x: i32) -> i32 {
x * x // No semicolons,Expression returns
}
// Multi-parameter
fn area(width: u32, height: u32) -> u32 {
width * height
}
// Return multiple values(Tuple)
fn min_max(a: i32, b: i32) -> (i32, i32) {
if a < b {
(a, b) // Tuples as Return Values
} else {
(b, a)
}
}
fn main() {
print_separator();
println!("5 the square of: {}", square(5));
println!("Area 10x20: {}", area(10, 20));
let (min, max) = min_max(100, 50);
println!("min={}, max={}", min, max);
}
Resultado:
-------------------
5 the square of: 25
Area 10x20: 200
min=50, max=100
min_maxRetorna uma tupla; quando chamada, ela é desestruturada e atribuída usandolet (min, max). Essa é a maneira padrão de “retornar vários valores” em Rust.
▶ Exemplo 2: Retorno antecipado e modo de guarda (Dificuldade ⭐⭐)
// ============================================
// Return Early:Inspection Criteria,If you're not satisfied, leave.
// ============================================
fn safe_divide(a: f64, b: f64) -> f64 {
if b == 0.0 {
println!("Error:The divisor cannot be 0");
return f64::NAN; // Return Early NaN(Not a Number)
}
a / b // Under normal circumstances,Expression returns
}
fn get_grade(score: i32) -> &'static str {
if score < 0 || score > 100 {
return "Invalid Fractions";
}
if score >= 90 { return "A"; }
if score >= 80 { return "B"; }
if score >= 70 { return "C"; }
if score >= 60 { return "D"; }
"F"
}
fn main() {
println!("10 / 3 = {}", safe_divide(10.0, 3.0));
println!("10 / 0 = {}", safe_divide(10.0, 0.0));
println!("Fractions 85: {}", get_grade(85));
println!("Fractions -5: {}", get_grade(-5));
}
Resultado:
10 / 3 = 3.3333333333333335
Error:The divisor cannot be 0
10 / 0 = NaN
Fractions 85: B
Fractions -5: Invalid Fractions
O retorno antecipado (
return) é comumente usado no padrão “verificar primeiro, depois executar”. Ao lidar com condições de erro e casos extremos logo no início, a lógica subsequente prossegue sem problemas dentro de uma “zona segura”.
▶ Exemplo 3: Expressões em bloco e teste de escopo (Dificuldade ⭐⭐)
// ============================================
// Scope and Return Values of Block Expressions
// ============================================
fn main() {
let outer = "Outer-scope variables".to_string();
let inner_result = {
let inner = "Nested variables".to_string();
println!("Accessible internally: {}", outer); // ✅ The inner layer can access the outer layer
// Shading Variables with the Same Name
let outer = 42;
println!("After masking outer: {}", outer); // 42,Not a string
inner.len() // Block expression return values:inner length
}; // inner Destroyed here
println!("The value returned by the block: {}", inner_result);
println!("External outer: {}", outer); // ✅ Outer layer outer Still
// println!("{}", inner); // ❌ Compilation Error:inner Not in this scope
}
Resultado:
Accessible internally: Outer-scope variables
After masking outer: 42
The value returned by the block: 8
External outer: Outer-scope variables
Regras de escopo dos blocos: os blocos internos podem acessar variáveis externas, mas os blocos externos não podem acessar variáveis internas. O sombreamento apenas “oculta” temporariamente as variáveis externas; assim que você sai do bloco interno, as variáveis externas voltam ao normal.
▶ Exemplo 4: Ponteiros de função e funções de ordem superior (Dificuldade ⭐⭐⭐)
// ============================================
// Passing Functions as Arguments——An Introduction to Higher-Order Functions
// ============================================
type MathOp = fn(i32, i32) -> i32;
fn add(a: i32, b: i32) -> i32 { a + b }
fn sub(a: i32, b: i32) -> i32 { a - b }
fn mul(a: i32, b: i32) -> i32 { a * b }
fn calculate(op: MathOp, a: i32, b: i32) -> i32 {
op(a, b)
}
fn apply_twice(f: fn(i32) -> i32, x: i32) -> i32 {
f(f(x))
}
fn main() {
let result1 = calculate(add, 10, 5);
let result2 = calculate(sub, 10, 5);
let result3 = calculate(mul, 10, 5);
println!("10 + 5 = {}", result1);
println!("10 - 5 = {}", result2);
println!("10 * 5 = {}", result3);
let double = |x: i32| x * 2;
let result4 = apply_twice(double, 3);
println!("double(double(3)) = {}", result4);
let increment = |x: i32| x + 1;
let result5 = apply_twice(increment, 5);
println!("increment(increment(5)) = {}", result5);
let alice_points = 80;
let bonus = |base: i32| -> i32 { base + 10 };
println!("Alice After the bonus points are added: {}", bonus(alice_points));
}
Resultado:
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
double(double(3)) = 12
increment(increment(5)) = 7
Alice After the bonus points are added: 90
Os ponteiros de função (do tipo
fn) e os closures (do tipo|x| x * 2) podem ser passados como argumentos para funções de ordem superior. Os ponteiros de função apontam para endereços de código determinados em tempo de compilação, enquanto os closures podem capturar variáveis de ambiente.
▶ Exemplo 5: Exercício abrangente — Processamento das notas dos alunos (Dificuldade ⭐⭐⭐)
// ============================================
// Comprehensive Example:Function,Combining Scope and Shading
// ============================================
fn average(scores: &[i32]) -> f64 {
if scores.is_empty() {
return 0.0;
}
let sum: i32 = scores.iter().sum();
sum as f64 / scores.len() as f64
}
fn classify(score: i32) -> &'static str {
match score {
90..=100 => "A",
80..=89 => "B",
70..=79 => "C",
60..=69 => "D",
_ => "F",
}
}
fn report(names: &[&str], scores: &[i32]) {
let avg = average(scores);
println!("Class Average Score: {:.1}", avg);
for (i, name) in names.iter().enumerate() {
let score = scores[i];
let grade = classify(score);
let status = if score >= 60 { "Through" } else { "Failed" };
println!("{}: {} pts, Level {}, {}", name, score, grade, status);
}
let passing = scores.iter().filter(|&&s| s >= 60).count();
println!("Number of people who passed: {}/{}", passing, scores.len());
}
fn main() {
let names = ["Alice", "Bob", "Charlie", "David"];
let scores = [95, 72, 58, 88];
println!("=== Report Card ===");
report(&names, &scores);
println!("\n--- Scope Demonstration ---");
let result = {
let scores = [100, 90, 80];
let avg = average(&scores);
format!("Average of the top three: {:.1}", avg)
};
println!("{}", result);
let avg = "Outer layer avg";
println!("Outer-scope variables: {}", avg);
}
Resultado:
=== Report Card ===
Class Average Score: 78.2
Alice: 95 pts, Level A, Through
Bob: 72 pts, Level C, Through
Charlie: 58 pts, Level F, Failed
David: 88 pts, Level B, Through
Number of people who passed: 3/4
--- Scope Demonstration ---
Average of the top three: 90.0
Outer-scope variables: Outer layer avg
Este exemplo combina o uso de definições de funções, parâmetros de fatia, classificação
match, métodos de iterador e ocultação de variáveis no escopo de bloco. Usar a fatia&[T]como parâmetro de função oferece a maior flexibilidade, e ocultar uma variável com o mesmo nome dentro do escopo de um bloco não afeta o escopo externo.
❓ Perguntas Frequentes
P: A expressão na última linha não inclui um ponto-e-vírgula como valor de retorno. O que devo fazer se costumo esquecer de adicionar o ponto-e-vírgula? R: Se você esquecer o ponto-e-vírgula, o compilador exibirá um erro, solicitando que você especifique se se trata de uma instrução ou de uma expressão. O compilador do Rust é muito inteligente nesse aspecto; ele perguntará: “Você esqueceu o ponto-e-vírgula?” ou “Deseja retornar um valor?”. Depois que você se acostumar, vai achar esse design bastante elegante.
P: Qual é a diferença entre
returne um retorno por expressão? R:returné usado para retornos antecipados, enquanto o retorno por expressão é usado no final de uma função. Você também pode usarreturnno final de uma função, mas a convenção é “usar uma expressão no final ereturnpara retornos antecipados”.
P: Por que os tipos dos parâmetros de função devem ser especificados? R: Porque o Rust não infere os tipos dos parâmetros de função. A assinatura de uma função é um contrato de API; especificar explicitamente os tipos deixa claro para quem a chama o que precisa ser passado. Essa é uma escolha de design do Rust: clareza em vez de concisão.
P: O shadowing é uma prática inadequada? R: O shadowing moderado é uma boa prática. Por exemplo, converter
Stringem&stre, em seguida, continuar a processá-la usando uma variável com o mesmo nome torna o código mais conciso. No entanto, você deve evitar o shadowing excessivo que leve à confusão — é melhor limitar a hierarquia de shadowing a no máximo dois níveis.
P: É possível aninhar funções? R: Sim, o Rust permite definir funções dentro de outras funções. As funções internas só são visíveis dentro do escopo da função em que estão definidas e não podem ser chamadas de fora desse escopo. Isso é útil quando você precisa de funções auxiliares, mas não quer expô-las ao exterior.
📖 Resumo
- As funções são definidas usando a palavra-chave
fn, e os parâmetros devem ser tipados. - Se a última expressão no corpo de uma função não terminar com um ponto-e-vírgula, ela servirá como valor de retorno.
returnPalavra-chave usada para devolução antecipada{}Os blocos definem o escopo; os blocos internos podem acessar as variáveis externas, mas não o contrário.- O sombreamento de variáveis permite que escopos internos declarem variáveis com o mesmo nome, substituindo temporariamente as variáveis nos escopos externos.
- Os parâmetros da função são visíveis em todo o corpo da função
📝 Exercícios
- Dificuldade ⭐: Escreva uma função
fn is_even(n: i32) -> boolque determine se um número inteiro é par e, em seguida, escrevamainpara chamá-la e exibir a paridade de cada número de 1 a 10. - Dificuldade ⭐⭐: Escreva uma função
fn max_of_three(a: i32, b: i32, c: i32) -> i32que retorne o maior valor entre três números. (Não é permitido usar a função.max()da biblioteca padrão; escreva sua própria lógica de comparação.) - Dificuldade ⭐⭐⭐: Escreva uma função
fn calculator(op: char, a: f64, b: f64) -> f64que execute a operação correspondente com base nos parâmetrosop('+''-''*''/') e retornef64::NANquando o divisor for 0. Teste essas quatro operações emmain.