Rust: Rust 函数与作用域:定义、传参、返回值与变量遮蔽

最后更新:2026-08-26

Rust 的函数是第一等公民——它的参数和返回值类型,是编译器为你把关的第一道防线。

函数是组织代码的基本单元。Rust 的函数语法和 C 系语言相似,但在参数模式匹配表达式返回值上有着独特风格。


1. 你将学到


2. 一个咖啡店的故事

(1) 痛苦:做咖啡没有标准化流程

Lisa 开了一家咖啡店,生意越来越好,但问题也来了:

"如果做咖啡能像调用函数一样稳定就好了:同样的输入,永远得到同样的输出。"

(2) Rust 函数的方案

RUST
// 做拿铁的"函数":输入参数,输出咖啡
fn make_latte(milk_ml: i32, coffee_ml: i32) -> String {
    let result = format!("拿铁完成:{}ml 牛奶 + {}ml 咖啡,香浓可口!", milk_ml, coffee_ml);
    result  // 没有分号——表达式返回值
}

fn main() {
    let my_coffee = make_latte(200, 30);
    println!("{}", my_coffee);

    let another = make_latte(150, 45);
    println!("{}", another);
}

函数就像咖啡配方:参数是原料(牛奶量、咖啡量),返回值是成品。同样的输入保证同样的输出——这就是函数的确定性


3. 函数定义

(1) 基本语法

RUST
fn function_name(param1: Type1, param2: Type2) -> ReturnType {
    // 函数体
    return_value  // 表达式返回(不加分号)
}
100%
graph TB
    A[fn 函数名] --> B[(参数列表)]
    A --> C[-> 返回类型]
    A --> D[{ 函数体 }]
    B --> E[每个参数: name: Type]
    D --> F[最后表达式 = 返回值]
    D --> G[return 关键字 = 提前返回]

(2) 参数与返回值的各种形式

形式 语法 示例
无参数无返回 fn foo() fn greet() { println!("hi"); }
有参数无返回 fn foo(x: i32) fn show(n: i32) { println!("{}", n); }
有参数有返回 fn foo(x: i32) -> i32 fn double(x: i32) -> i32 { x * 2 }
多个返回值(元组) fn foo() -> (i32, bool) fn stats() -> (i32, bool) { (42, true) }
提前返回 return value; if x < 0 { return 0; }

核心规则:函数体最后一个表达式就是返回值(不加分号)。用 return 关键字做提前返回。

(3) 参数传递方式

传递方式 语法 所有权变化 适用场景
传值 fn foo(s: String) 所有权移入函数 函数需要消费该值
传不可变引用 fn foo(s: &String) 借用,所有权不变 只需读取数据
传可变引用 fn foo(s: &mut String) 借用,所有权不变 需要修改数据

(4) 返回值方式对比

方式 语法 所有权变化 示例
表达式返回 末行不加分号 所有权转出 fn f() -> String { s }
return 返回 return value; 所有权转出 if err { return None; }
元组多返回 -> (T1, T2) 多个所有权转出 fn f() -> (i32, bool)
输出参数 &mut T 不转移所有权 fn fill(buf: &mut Vec<i32>)

4. 作用域与遮蔽

(1) 块作用域

Rust 的作用域由 {} 界定。每个块都有自己的作用域:

RUST
fn main() {
    let x = 10;          // 外层作用域

    {
        let y = 20;      // 内层作用域,y 只在这里可用
        println!("内部: x={}, y={}", x, y);  // ✅ 可以访问外层 x
    }

    // println!("{}", y);  // ❌ 编译错误:y 不在此作用域

    println!("外部: x={}", x);  // ✅
}

(2) 变量遮蔽(Shadowing)

外层变量可以被内层同名变量遮蔽,内层代码只能看到内层变量:

RUST
fn main() {
    let x = 1;            // 外层 x = 1

    {
        let x = 2;        // 内层 x 遮蔽了外层 x
        println!("内部 x = {}", x);  // 输出: 内部 x = 2
    }

    println!("外部 x = {}", x);       // 输出: 外部 x = 1
}

(3) 函数参数的作用域

函数参数在整个函数体内部可见:

RUST
fn foo(x: i32) {
    // x 在整个函数体内部可用
    let y = x + 1;
    println!("x={}, y={}", x, y);
}  // 函数结束,x 和 y 都被销毁

5. 函数示例

▶ 示例 1:各种函数签名(难度 ⭐)

RUST
// ============================================
// 不同参数和返回类型组合的函数
// ============================================

// 无参数无返回值
fn print_separator() {
    println!("-------------------");
}

// 单参数有返回值
fn square(x: i32) -> i32 {
    x * x  // 不加分号,表达式返回
}

// 多参数
fn area(width: u32, height: u32) -> u32 {
    width * height
}

// 返回多个值(元组)
fn min_max(a: i32, b: i32) -> (i32, i32) {
    if a < b {
        (a, b)  // 元组作为返回值
    } else {
        (b, a)
    }
}

fn main() {
    print_separator();
    println!("5 的平方: {}", square(5));
    println!("面积 10x20: {}", area(10, 20));

    let (min, max) = min_max(100, 50);
    println!("min={}, max={}", min, max);
}

输出:

TEXT 📖 仅展示
-------------------
5 的平方: 25
面积 10x20: 200
min=50, max=100

min_max 返回元组,调用时用 let (min, max) 解构赋值。这是 Rust 中"返回多个值"的标准做法。


▶ 示例 2:提前返回与守卫模式(难度 ⭐⭐)

RUST
// ============================================
// 提前返回:检查条件,不满足就退出
// ============================================

fn safe_divide(a: f64, b: f64) -> f64 {
    if b == 0.0 {
        println!("错误:除数不能为 0");
        return f64::NAN;  // 提前返回 NaN(Not a Number)
    }
    a / b  // 正常情况,表达式返回
}

fn get_grade(score: i32) -> &'static str {
    if score < 0 || score > 100 {
        return "无效分数";
    }
    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!("分数 85: {}", get_grade(85));
    println!("分数 -5: {}", get_grade(-5));
}

输出:

TEXT 📖 仅展示
10 / 3 = 3.3333333333333335
错误:除数不能为 0
10 / 0 = NaN
分数 85: B
分数 -5: 无效分数

提前返回(return)常用于"先检查后执行"模式。把错误条件和边界情况在最前面处理掉,后面就是"安全区"的顺利逻辑。


▶ 示例 3:块表达式与作用域测试(难度 ⭐⭐)

RUST
// ============================================
// 块表达式的作用域和返回值
// ============================================

fn main() {
    let outer = "外层变量".to_string();

    let inner_result = {
        let inner = "内层变量".to_string();
        println!("内部可以访问: {}", outer);  // ✅ 内层可以访问外层

        // 遮蔽同名变量
        let outer = 42;
        println!("遮蔽后的 outer: {}", outer);  // 42,不是字符串

        inner.len()  // 块表达式返回值:inner 的长度
    };  // inner 在此处被销毁

    println!("块返回的值: {}", inner_result);
    println!("外部 outer: {}", outer);  // ✅ 外层 outer 还在

    // println!("{}", inner);  // ❌ 编译错误:inner 不在此作用域
}

输出:

TEXT 📖 仅展示
内部可以访问: 外层变量
遮蔽后的 outer: 42
块返回的值: 8
外部 outer: 外层变量

块作用域规则:内层可以访问外层变量,外层不能访问内层变量。遮蔽只是暂时"屏蔽"了外层变量,离开内层块后外层变量恢复正常。


▶ 示例 4:函数指针与高阶函数(难度 ⭐⭐⭐)

RUST
// ============================================
// 函数作为参数传递——高阶函数入门
// ============================================

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 加分后: {}", bonus(alice_points));
}

输出:

TEXT 📖 仅展示
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
double(double(3)) = 12
increment(increment(5)) = 7
Alice 加分后: 90

函数指针(fn 类型)和闭包(|x| x * 2)都可以作为参数传递给高阶函数。函数指针指向编译时确定的代码地址,闭包可以捕获环境变量。


▶ 示例 5:综合练习——学生成绩处理(难度 ⭐⭐⭐)

RUST
// ============================================
// 综合示例:函数、作用域和遮蔽的组合运用
// ============================================

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!("班级平均分: {:.1}", avg);

    for (i, name) in names.iter().enumerate() {
        let score = scores[i];
        let grade = classify(score);
        let status = if score >= 60 { "通过" } else { "未通过" };
        println!("{}: {} 分, 等级 {}, {}", name, score, grade, status);
    }

    let passing = scores.iter().filter(|&&s| s >= 60).count();
    println!("通过人数: {}/{}", passing, scores.len());
}

fn main() {
    let names = ["Alice", "Bob", "Charlie", "David"];
    let scores = [95, 72, 58, 88];

    println!("=== 成绩报告 ===");
    report(&names, &scores);

    println!("\n--- 作用域演示 ---");
    let result = {
        let scores = [100, 90, 80];
        let avg = average(&scores);
        format!("前三名平均: {:.1}", avg)
    };
    println!("{}", result);

    let avg = "外层 avg";
    println!("外层变量: {}", avg);
}

输出:

TEXT 📖 仅展示
=== 成绩报告 ===
班级平均分: 78.2
Alice: 95 分, 等级 A, 通过
Bob: 72 分, 等级 C, 通过
Charlie: 58 分, 等级 F, 未通过
David: 88 分, 等级 B, 通过
通过人数: 3/4

--- 作用域演示 ---
前三名平均: 90.0
外层变量: 外层 avg

此示例综合运用了函数定义、切片参数、match 分类、迭代器方法和块作用域遮蔽。函数参数用 &[T] 切片最灵活,块作用域内遮蔽同名变量不影响外层。


❓ 常见问题

Q 最后一行表达式不加分号作为返回值,容易忘加分号怎么办?
A 忘了加分号编译器会报错,提示你应该是语句还是表达式。
Q return 和表达式返回有什么区别?
A return 用于提前返回,表达式返回用于函数末尾。
Q 函数参数为什么必须写类型?
A 因为 Rust 不会推断函数参数的类型。
Q 遮蔽(shadowing)是不是不好的习惯?
A 适度遮蔽是好的。
Q 函数可以嵌套吗?
A 可以,Rust 支持函数内部定义函数。

📖 小节


📝 作业

  1. 难度 ⭐:写一个函数 fn is_even(n: i32) -> bool,判断整数是否为偶数,然后写 main 调用它打印 1 到 10 每个数的奇偶性。
  2. 难度 ⭐⭐:写一个函数 fn max_of_three(a: i32, b: i32, c: i32) -> i32,返回三个数的最大值。(不允许用标准库的 .max(),自己写比较逻辑。)
  3. 难度 ⭐⭐⭐:写一个函数 fn calculator(op: char, a: f64, b: f64) -> f64,根据 op 参数('+' '-' '*' '/')做对应运算,除数为 0 时返回 f64::NAN。在 main 中测试这 4 种运算。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

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

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