3. 函数与控制流:构建程序逻辑的骨架
任何有实际价值的程序,都离不开两个基本要素:函数(将代码组织为可复用的单元)与控制流(决定代码执行的路径与顺序)。在 Rust 中,函数是模块化与抽象的基础,而控制流则赋予程序决策与循环的能力。本章将深入剖析 Rust 函数的定义、参数传递与返回值机制,并系统讲解表达式与语句的区别,随后逐一拆解 if/else、loop、while、for 四种循环结构,以及 break 和 continue 在循环中的精确控制技巧。理解这些内容,你将能够编写出结构清晰、逻辑严谨的 Rust 代码。
3.1 函数定义:从基础语法到参数传递
Rust 的函数定义以 fn 关键字开始,后跟函数名、参数列表、返回类型以及函数体。函数名采用蛇形命名法(snake_case),参数必须显式标注类型,这是 Rust 静态类型系统的核心要求。函数体由一系列语句和表达式组成,最后一个表达式的值即为函数的返回值(若没有返回值,则返回单元类型 ())。
// 无返回值函数
fn greet(name: &str) {
println!("Hello, {}!", name);
}
// 有返回值函数
fn add(a: i32, b: i32) -> i32 {
a + b // 注意:没有分号,作为表达式返回
}
// 使用 return 关键字提前返回
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
return None;
}
Some(a / b)
}
参数传递是函数设计的核心环节。Rust 中的参数传递遵循所有权规则:默认情况下,值会被移动(move)到函数内部,函数外部将无法再使用该变量。若希望函数只借用数据而不获取所有权,可以使用引用(&T)或可变引用(&mut T)。这一机制在保证内存安全的同时,也要求开发者明确设计数据的归属关系。
&str 而非 String,处理数组时使用 &[T] 而非 Vec<T>。
3.2 表达式与语句:理解 Rust 的求值哲学
Rust 是一门基于表达式的语言,这一特性深刻影响了代码的编写方式。表达式(expression)会计算并产生一个值,而语句(statement)只执行操作,不产生值。函数体、循环体、if 分支等块结构({})本身也是表达式,其最后一个表达式的值即为整个块的值。
| 类别 | 示例 | 是否产生值 | 典型用途 |
|---|---|---|---|
| 表达式 | a + b、x * 2、if cond { 1 } else { 2 } | 是 | 赋值、返回值、条件计算 |
| 语句 | let x = 5;、println!("hi");、foo(); | 否 | 声明变量、执行操作 |
| 块表达式 | { let y = 3; y + 1 } | 是(值为 y+1) | 局部作用域计算 |
理解表达式与语句的区别,对于编写简洁的 Rust 代码至关重要。例如,使用 if 作为表达式可以直接将条件结果赋值给变量:
let condition = true;
let number = if condition { 5 } else { 6 };
// number 的值为 5
这种写法比传统的先声明后赋值更加紧凑,且避免了可变变量的使用,符合 Rust 的不可变性偏好。需要注意的是,if 作为表达式时,所有分支必须返回相同类型的值,否则编译器会报错。
3.3 循环控制:loop、while 与 for 的实战应用
Rust 提供了三种循环结构,每种都有其适用场景。loop 用于创建无限循环,通常配合 break 在满足特定条件时退出;while 在每次迭代前检查条件,适合条件驱动的循环;for 则用于遍历集合或范围,是 Rust 中最常用的循环形式。
// loop 无限循环 + break 退出
let mut counter = 0;
loop {
counter += 1;
if counter == 10 {
break;
}
}
// while 条件循环
let mut n = 3;
while n > 0 {
println!("{}", n);
n -= 1;
}
// for 遍历范围
for i in 0..5 {
println!("{}", i); // 输出 0 到 4
}
// for 遍历数组
let arr = [10, 20, 30];
for element in arr.iter() {
println!("{}", element);
}
break 和 continue 是循环控制的关键工具。break 用于立即终止当前循环,而 continue 则跳过本次迭代的剩余部分,进入下一次迭代。在嵌套循环中,可以使用循环标签(loop label)来指定 break 或 continue 作用于哪一层循环:
'outer: for i in 0..5 {
for j in 0..5 {
if i * j == 6 {
break 'outer; // 直接跳出外层循环
}
println!("i={}, j={}", i, j);
}
}
loop 时务必确保存在明确的退出条件,否则程序将陷入死循环。在嵌入式或系统编程中,死循环可能导致系统无响应。建议在循环体内设置计数器或状态标志,并定期检查。
3.4 案例拆解:实现一个命令行计算器
为了综合运用本章知识,我们设计一个简单的命令行计算器,支持加、减、乘、除四种运算,并能够连续计算直到用户输入 quit 退出。该案例将展示函数定义、参数传递、循环控制以及表达式求值的完整流程。
背景与目标:用户通过标准输入输入表达式(如 3 + 5),程序解析并计算结果,然后继续等待下一个输入。输入 quit 时退出程序。
关键实现步骤:
- 定义
calculate函数,接收操作数和运算符,返回计算结果。 - 使用
loop创建主循环,读取用户输入。 - 使用
trim()和split_whitespace()解析输入字符串。 - 匹配运算符并调用
calculate,处理除零错误。 - 使用
break退出循环。
use std::io;
fn calculate(a: f64, op: char, b: f64) -> Result<f64, String> {
match op {
'+' => Ok(a + b),
'-' => Ok(a - b),
'*' => Ok(a * b),
'/' => {
if b == 0.0 {
Err("Division by zero".to_string())
} else {
Ok(a / b)
}
}
_ => Err("Invalid operator".to_string()),
}
}
fn main() {
println!("Simple Calculator (type 'quit' to exit)");
loop {
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Failed to read line");
let input = input.trim();
if input == "quit" {
break;
}
let parts: Vec<&str> = input.split_whitespace().collect();
if parts.len() != 3 {
println!("Invalid format. Use: number operator number");
continue;
}
let a: f64 = match parts[0].parse() {
Ok(num) => num,
Err(_) => { println!("Invalid number: {}", parts[0]); continue; }
};
let op: char = match parts[1].chars().next() {
Some(c) => c,
None => { println!("Missing operator"); continue; }
};
let b: f64 = match parts[2].parse() {
Ok(num) => num,
Err(_) => { println!("Invalid number: {}", parts[2]); continue; }
};
match calculate(a, op, b) {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {}", e),
}
}
println!("Goodbye!");
}
结果与复盘:该程序成功实现了连续计算功能,通过 loop 保持程序运行,利用 break 响应退出指令,使用 continue 跳过格式错误的输入。在实现过程中,我们注意到 Rust 的 match 表达式非常适合处理多分支逻辑,而 Result 类型则优雅地处理了除零等异常情况。此案例展示了函数与控制流在实际项目中的协同工作方式。
3.5 常见误区与最佳实践
在编写 Rust 函数和控制流时,初学者常犯以下错误,需要特别注意:
- 忘记分号导致返回值错误:函数体最后一个表达式若误加分号,会变成语句,导致返回类型变为
(),与声明的返回类型不匹配。 - 无限循环无退出条件:使用
loop时未设置break条件,或条件永远无法满足。 - 所有权转移导致外部变量失效:将变量直接传入函数而未使用引用,函数外部将无法继续使用该变量。
- 循环内重复分配:在
for循环内创建大型数据结构,导致性能下降。应尽量在循环外分配,循环内复用。 - 忽略
continue的语义:在while循环中,continue会跳过条件检查,直接进入下一次迭代,可能导致条件变量未更新而陷入死循环。
for 循环遍历集合,利用迭代器方法(如 map、filter)替代显式循环;函数参数尽量使用引用类型;对于多返回值场景,使用元组或结构体;在循环中明确退出条件,并考虑使用 while let 处理模式匹配循环。
通过本章的学习,你已经掌握了 Rust 函数与控制流的核心机制。这些基础构件将支撑你后续编写更复杂的程序逻辑。在实际编码中,请时刻关注所有权规则与表达式求值的特点,这将帮助你写出既高效又安全的 Rust 代码。