Rust: Rust Functions and Scope

Last updated: 2026-08-26

Functions are first-class citizens in Rust—their parameter and return types serve as the compiler's first line of defense.

Functions are the basic building blocks of code. Rust's function syntax is similar to that of C-family languages, but it has a unique style when it comes to argument pattern matching and expression return values.


1. What You'll Learn



2. The Story of a Coffee Shop

(1) The struggle: There's no standardized process for making coffee

Lisa opened a coffee shop, and business has been getting better and better, but problems have also arisen:

"It would be great if making coffee were as consistent as calling a function: the same input always produces the same output."

(2) Approach for Rust Functions

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);
}

Functions are like coffee recipes: the parameters are the ingredients (amount of milk, amount of coffee), and the return value is the finished product. The same input guarantees the same output—this is the determinism of functions.



3. Function Definitions

(1) Basic Syntax

RUST
fn function_name(param1: Type1, param2: Type2) -> ReturnType {
    // Function Body
    return_value  // Expression returns(No semicolons)
}
100%
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) Various Forms of Parameters and Return Values

Form Syntax Example
No parameters, no return value fn foo() fn greet() { println!("hi"); }
Takes parameters, returns nothing fn foo(x: i32) fn show(n: i32) { println!("{}", n); }
With parameters and a return value fn foo(x: i32) -> i32 fn double(x: i32) -> i32 { x * 2 }
Multiple return values (tuple) fn foo() -> (i32, bool) fn stats() -> (i32, bool) { (42, true) }
Go Back return value; if x < 0 { return 0; }

Core Rule: The last expression in the function body is the return value (no semicolon). Use the return keyword for an early return.

(3) Parameter Passing Methods

Passing Method Syntax Change in Ownership Applicable Scenarios
Pass-by-value fn foo(s: String) Ownership is transferred to the function The function needs to consume the value
Pass by immutable reference fn foo(s: &String) Borrow, ownership remains unchanged Read-only
Pass by reference fn foo(s: &mut String) Borrow, ownership remains unchanged Data needs to be modified

(4) Comparison of Return Values

Method Syntax Ownership Change Example
Expression Returns No Semicolon at the End of a Line Transfer of Ownership fn f() -> String { s }
Return return value; Transfer Ownership if err { return None; }
Multiple Tuple Returns -> (T1, T2) Multiple Ownership Transfers fn f() -> (i32, bool)
Output Parameter &mut T No ownership transfer fn fill(buf: &mut Vec<i32>)


4. Scope and Shading

(1) Block Scope

Scope in Rust is defined by {}. Each block has its own scope:

RUST
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) Variable Shadowing

Outer variables can be shadowed by inner variables with the same name, and the inner code can only access the inner variables:

RUST
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) Scope of Function Parameters

Function parameters are visible throughout the entire function body:

RUST
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. Function Examples

▶ Example 1: Various Function Signatures (Difficulty ⭐)

Output:

TEXT 📖 Display only
-------------------
5 the square of: <square(5)>
Area 10x20: <area(10, 20)>
min=<min>, max=<max>
RUST
// ============================================
// 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);
}

Output:

TEXT 📖 Display only
-------------------
5 the square of: 25
Area 10x20: 200
min=50, max=100

min_max Returns a tuple; when called, it is destructured and assigned using let (min, max). This is the standard way to "return multiple values" in Rust.


▶ Example 2: Early Return and Guard Mode (Difficulty ⭐⭐)

Output:

TEXT 📖 Display only
Error:The divisor cannot be 0
10 / 3 = <safe_divide(10.0, 3.0)>
10 / 0 = <safe_divide(10.0, 0.0)>
Fractions 85: <get_grade(85)>
Fractions -5: <get_grade(-5)>
RUST
// ============================================
// 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));
}

Output:

TEXT 📖 Display only
10 / 3 = 3.3333333333333335
Error:The divisor cannot be 0
10 / 0 = NaN
Fractions 85: B
Fractions -5: Invalid Fractions

Early return (return) is commonly used in the "check first, then execute" pattern. By handling error conditions and edge cases at the very beginning, the subsequent logic proceeds smoothly within a "safe zone."


▶ Example 3: Block Expressions and Scope Testing (Difficulty ⭐⭐)

Output:

TEXT 📖 Display only
Accessible internally: 42
After masking outer: 42
The value returned by the block: <inner_result>
External outer: 42
Nested variables
RUST
// ============================================
// 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
}

Output:

TEXT 📖 Display only
10 + 5 = <result1>
10 - 5 = <result2>
10 * 5 = <result3>
double(double(3)) = <result4>
increment(increment(5)) = <result5>
Alice After the bonus points are added: <bonus(alice_points)>



Block scope rules: Inner blocks can access outer variables, but outer blocks cannot access inner variables. Shading merely temporarily "masks" outer variables; once you exit the inner block, the outer variables return to normal.


▶ Example 4: Function Pointers and Higher-Order Functions (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
10 + 5 = <result1>
10 - 5 = <result2>
10 * 5 = <result3>
double(double(3)) = <result4>
increment(increment(5)) = <result5>
Alice After the bonus points are added: <bonus(alice_points)>
RUST
// ============================================
// 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));
}

Output:

TEXT 📖 Display only
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

Function pointers (of type fn) and closures (of type |x| x * 2) can both be passed as arguments to higher-order functions. Function pointers point to code addresses determined at compile time, while closures can capture environment variables.


▶ Example 5: Comprehensive Exercise—Processing Student Grades (Difficulty ⭐⭐⭐)

Output:

TEXT 📖 Display only
Class Average Score: Outer layer avg
<name>: <score> pts, Level <grade>, <status>
Number of people who passed: <passing>/3
=== Report Card ===

--- Scope Demonstration ---
<result>
Outer-scope variables: Outer layer avg
RUST
// ============================================
// 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);
}

Output:

TEXT 📖 Display only
=== 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

This example combines the use of function definitions, slice parameters, match classification, iterator methods, and block scope shadowing. Using the &[T] slice as a function parameter offers the greatest flexibility, and shadowing a variable with the same name within a block scope does not affect the outer scope.


❓ FAQ

Q The expression in the last line doesn't include a semicolon as a return value. What should I do if I tend to forget to add the semicolon?
A If you forget the semicolon, the compiler will throw an error, prompting you to specify whether it's a statement or an expression.
Q What is the difference between return and an expression return?
A return is used for early returns, while an expression return is used at the end of a function.
Q Why must function parameters have their types specified?
A Because Rust does not infer the types of function parameters.
Q Is shadowing a bad practice?
A Moderate shadowing is a good practice.
Q Can functions be nested?
A Yes, Rust supports defining functions inside other functions.

📖 Summary


📝 Exercises

  1. Difficulty ⭐: Write a function fn is_even(n: i32) -> bool that determines whether an integer is even, and then write main to call it and print the parity of each number from 1 to 10.
  2. Difficulty ⭐⭐: Write a function fn max_of_three(a: i32, b: i32, c: i32) -> i32 that returns the maximum of three numbers. (You may not use the standard library's .max(); write your own comparison logic.)
  3. Difficulty ⭐⭐⭐: Write a function fn calculator(op: char, a: f64, b: f64) -> f64 that performs the corresponding operation based on the parameters op ('+' '-' '*' '/'), and returns f64::NAN when the divisor is 0. Test these four operations in main.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏