Rust 的流程控制与函数
流程控制和函数是任何编程语言的核心组成部分,Rust 也不例外。
本章介绍了 Rust 中的流程控制与函数相关知识,包括:
- 条件判断:
if-else
结构和if let
模式匹配 - 循环结构:
loop
(无限循环)、while
(条件循环)和for
(迭代循环) - 函数:定义、参数、返回值和函数指针的使用
- 函数高级特性:默认参数的模拟、可变参数的实现和递归函数
- 代码块与作用域:作用域规则和变量遮蔽特性
一、条件判断(if-else、if let)
Rust 的条件判断用于根据不同条件执行不同代码分支,主要包括if-else
结构和if let
模式匹配。
1.1 if-else 基本用法
Rust 的if
条件不需要用括号()
包裹,但其代码块必须用{}
包围。条件表达式的结果必须是bool
类型(Rust 不会自动进行类型转换)。
fn main() {let age = 18;if age >= 18 {println!("已成年");} else if age >= 13 {println!("青少年");} else {println!("儿童");}// 错误示例:条件必须是bool类型// let num = 5;// if num { // 编译错误:expected `bool`, found `i32`// println!("num is non-zero");// }
}
if
表达式可以作为赋值语句的一部分,因为 Rust 中一切都是表达式(有返回值):
fn main() {let score = 85;let grade = if score >= 90 {'A'} else if score >= 80 {'B'} else {'C'};println!("成绩等级: {}", grade); // 输出:成绩等级: B
}
1.2 if let 模式匹配
if let
是一种简化的模式匹配,适用于只关心一种匹配情况的场景,比match
表达式更简洁。常用于处理Option
类型或枚举。
fn main() {// 处理Option类型let some_value: Option<i32> = Some(5);// 传统match方式match some_value {Some(x) => println!("找到值: {}", x),None => (), // 不关心None的情况,只做占位}// 等价的if let方式if let Some(x) = some_value {println!("找到值: {}", x); // 输出:找到值: 5}// 结合else处理不匹配的情况let another_value: Option<i32> = None;if let Some(x) = another_value {println!("找到值: {}", x);} else {println!("没有值"); // 输出:没有值}// 处理枚举enum Fruit {Apple,Banana(String),}let fruit = Fruit::Banana("黄香蕉".to_string());if let Fruit::Banana(color) = fruit {println!("这是{}的香蕉", color); // 输出:这是黄香蕉的香蕉}
}
二、循环结构(loop、while、for 与迭代器)
Rust 提供了三种循环结构:loop
(无限循环)、while
(条件循环)和for
(迭代循环),其中for
循环与迭代器结合使用最为常见。
2.1 loop 无限循环
loop
创建一个无限循环,必须使用break
才能退出。它的特殊之处是可以返回一个值。
fn main() {// 基本用法let mut count = 0;loop {count += 1;if count == 3 {println!("计数到3,退出循环"); // 输出:计数到3,退出循环break; // 退出循环}}// 带返回值的looplet mut sum = 0;let result = loop {sum += 5;if sum >= 20 {break sum; // 返回sum的值}};println!("循环返回值: {}", result); // 输出:循环返回值: 20
}
2.2 while 条件循环
while
循环在条件为true
时持续执行,适合不确定循环次数的场景。
fn main() {let mut number = 3;while number > 0 {println!("{}!", number);number -= 1;}println!("发射!");// 输出:// 3!// 2!// 1!// 发射!
}
2.3 for 循环与迭代器
for
循环是 Rust 中最常用的循环方式,通常与迭代器配合使用,用于遍历集合或范围。
fn main() {// 遍历范围(左闭右开)for i in 1..5 { // 1到4(不包含5)println!("{}", i); // 输出:1 2 3 4}// 遍历数组let fruits = ["苹果", "香蕉", "橙子"];for fruit in fruits {println!("水果: {}", fruit);}// 使用迭代器方法enumerate获取索引和值for (index, value) in fruits.iter().enumerate() {println!("索引{}: {}", index, value);// 输出:// 索引0: 苹果// 索引1: 香蕉// 索引2: 橙子}// 遍历集合(需要引入标准库)use std::collections::VecDeque;let mut queue = VecDeque::new();queue.push_back("a");queue.push_back("b");for item in queue {println!("队列元素: {}", item);}
}
Rust 的迭代器提供了丰富的方法(如map
、filter
、collect
等),可以进行复杂的数据处理:
fn main() {let numbers = 1..10;// 过滤偶数并乘以2let result: Vec<i32> = numbers.filter(|x| x % 2 == 0).map(|x| x * 2).collect();println!("处理结果: {:?}", result); // 输出:处理结果: [4, 8, 12, 16]
}
三、函数定义与调用(参数、返回值、函数指针)
函数是代码复用和逻辑组织的基本单元,Rust 中使用fn
关键字定义函数。
3.1 函数定义与调用
Rust 函数的基本结构:fn 函数名(参数列表) -> 返回值类型 { 函数体 }
// 定义一个简单函数
fn greet(name: &str) {println!("Hello, {}!", name);
}// 带返回值的函数
fn add(a: i32, b: i32) -> i32 {a + b // 表达式末尾没有分号,作为返回值
}// 多个返回值(通过元组实现)
fn split_name(full_name: &str) -> (&str, &str) {let parts: Vec<&str> = full_name.split_whitespace().collect();(parts[0], parts[1]) // 返回元组
}fn main() {// 调用函数greet("Rust"); // 输出:Hello, Rust!let sum = add(3, 5);println!("3 + 5 = {}", sum); // 输出:3 + 5 = 8let (first, last) = split_name("John Doe");println!("名: {}, 姓: {}", first, last); // 输出:名: John, 姓: Doe
}
注意:
函数参数必须指定类型
函数返回值通过->指定类型
函数体中最后一个表达式(不带分号)作为返回值,也可以使用return关键字提前返回
3.2 函数参数的可变性
Rust 函数参数默认是不可变的,若要在函数内修改参数,需要显式声明mut
:
fn main() {let mut x = 5;modify_value(&mut x); // 传递可变引用println!("修改后的值: {}", x); // 输出:修改后的值: 10
}// 接收可变引用参数
fn modify_value(num: &mut i32) {*num *= 2; // 使用*解引用
}
3.3 函数指针
函数指针(function pointer)允许将函数作为参数传递或存储在变量中,类型表示为fn(参数类型) -> 返回值类型
。
// 定义几个数学函数
fn add(a: i32, b: i32) -> i32 {a + b
}fn subtract(a: i32, b: i32) -> i32 {a - b
}// 接收函数指针作为参数
fn calculate(operation: fn(i32, i32) -> i32, x: i32, y: i32) -> i32 {operation(x, y)
}fn main() {let a = 10;let b = 5;// 传递函数作为参数let sum = calculate(add, a, b);let diff = calculate(subtract, a, b);println!("{} + {} = {}", a, b, sum); // 输出:10 + 5 = 15println!("{} - {} = {}", a, b, diff); // 输出:10 - 5 = 5// 存储函数指针let op: fn(i32, i32) -> i32 = add;println!("使用函数指针: {}", op(3, 4)); // 输出:7
}
四、函数的高级特性(默认参数、可变参数、递归)
Rust 的函数系统提供了一些高级特性,虽然有些特性的实现方式与其他语言不同,但能满足复杂的编程需求。
4.1 默认参数
Rust没有内置的默认参数语法,但可以通过以下方式模拟:
使用结构体 + 默认实现
// 定义配置结构体
#[derive(Default)]
struct Config {timeout: u32,retries: u8,
}// 接收配置参数的函数
fn connect(config: Config) {println!("连接配置 - 超时: {}ms, 重试次数: {}", config.timeout, config.retries);
}fn main() {// 使用默认配置connect(Config::default()); // 输出:连接配置 - 超时: 0ms, 重试次数: 0// 自定义部分配置connect(Config {timeout: 5000,..Config::default() // 其余使用默认值}); // 输出:连接配置 - 超时: 5000ms, 重试次数: 0
}
使用函数重载
// 不使用 derive(Default),而是手动实现
struct Config {timeout: u32,retries: u8,
}// 手动实现 Default trait,定义自定义默认值
impl Default for Config {fn default() -> Self {Config {timeout: 5000, // 自定义默认超时为 5000msretries: 3, // 自定义默认重试次数为 3 次}}
}// 接收配置参数的函数
fn connect(config: Config) {println!("连接配置 - 超时: {}ms, 重试次数: {}", config.timeout, config.retries);
}fn main() {// 使用默认配置connect(Config::default()); // 输出:连接配置 - 超时: 0ms, 重试次数: 0// 自定义部分配置connect(Config {timeout: 1000,..Config::default() // 其余使用默认值}); // 输出:连接配置 - 超时: 5000ms, 重试次数: 0
}
4.2 定义接口
在 Rust 中,trait
(特质)是一种定义方法集合的机制,类似于其他语言中的 “接口(interface)”,但功能更灵活。它主要用于:
- 定义一组行为(方法)的规范,要求实现者必须提供这些行为的具体逻辑;
- 实现代码复用和多态(同一接口的不同实现)。
通俗理解:trait
是 “行为规范”
可以把 trait
想象成一份 “协议”,它规定了 “实现者必须具备哪些能力(方法)”。例如,Greet
trait 定义了 “打招呼” 的能力,任何类型(结构体、枚举等)只要只要实现 Greet
,就必须提供 greet
方法的具体实现。
基本用法示例
// 定义一个 trait(行为规范):要求实现者必须有 greet 方法
trait Greet {// 声明方法(只定义签名,不写实现)fn greet(&self);
}// 定义一个结构体(实现者1)
struct Person {name: String,
}// 为 Person 实现 Greet trait(遵守规范)
impl Greet for Person {fn greet(&self) {println!("Hello, I'm {}", self.name);}
}// 定义另一个结构体(实现者2)
struct Robot {model: String,
}// 为 Robot 实现 Greet trait(不同实现)
impl Greet for Robot {fn greet(&self) {println!("Greetings, human. I am {}", self.model);}
}fn main() {let alice = Person { name: "Alice".to_string() };let r2d2 = Robot { model: "R2-D2".to_string() };// 调用各自的 greet 方法(多态:同一方法名,不同实现)alice.greet(); // 输出:Hello, I'm Alicer2d2.greet(); // 输出:Greetings, human. I am R2-D2
}
trait
的核心特性
1.强制实现:
任何类型要使用 trait
,必须实现其所有方法(除非方法有默认实现)。
2.默认方法:
可以在 trait
中直接提供方法的默认实现,实现者可以选择覆盖或直接使用:
trait Greet {// 带默认实现的方法fn greet(&self) {println!("Hello!");}
}struct Cat;
struct Dog;
// 不覆盖默认方法,直接使用
impl Greet for Cat {}
// 覆盖默认方法
impl Greet for Dog {fn greet(&self) {println!("Hello! Dog!");}
}
fn main() {Cat.greet(); // 输出:Hello!Dog.greet(); // 输出:Hello! Dog!
}
3. 规范参数 / 返回值:
可以用 trait
约束函数的参数或返回值,实现 “泛型多态”:
// 定义一个 trait(行为规范):要求实现者必须有 greet 方法
trait Greet {// 声明方法(只定义签名,不写实现)fn greet(&self);
}// 定义一个结构体(实现者1)
struct Person {name: String,
}// 为 Person 实现 Greet trait(遵守规范)
impl Greet for Person {fn greet(&self) {println!("Hello, I'm {}", self.name);}
}// 定义另一个结构体(实现者2)
struct Robot {model: String,
}// 为 Robot 实现 Greet trait(不同实现)
impl Greet for Robot {fn greet(&self) {println!("Greetings, human. I am {}", self.model);}
}// 接受任何实现了 Greet 的类型
fn say_hello(who: &impl Greet) {who.greet();
}fn main() {let alice = Person { name: "Alice".to_string() };let r2d2 = Robot { model: "R2-D2".to_string() };// 调用各自的 greet 方法(多态:同一方法名,不同实现)alice.greet(); // 输出:Hello, I'm Alicer2d2.greet(); // 输出:Greetings, human. I am R2-D2say_hello(&alice); // 合法(Person 实现了 Greet)say_hello(&r2d2); // 合法(Robot 实现了 Greet)
}
4. 与 “接口” 的区别
虽然类似其他语言的接口,trait
有更灵活的特性:
- 可以为任何类型实现
trait
(包括标准库类型,如i32
、String
等); - 支持 “默认方法”,减少重复代码;
- 可以通过
trait
组合实现复杂行为。
总之,trait
是 Rust 中定义行为规范的核心机制,它:
- 规定了类型必须实现的方法;
- 支持多态和代码复用;
- 是 Rust 实现 “接口抽象” 的主要方式。
通过 trait
,你可以写出更灵活、可扩展的代码,尤其是在需要处理多种类型但统一行为的场景(如通用算法、工具函数等)。
4.3 可变参数
Rust 支持可变参数(variable arguments),通常通过宏或标准库中的std::fmt::Arguments
实现:
1. 使用宏定义可变参数函数
// 定义可变参数宏
macro_rules! sum {// 递归终止条件($x:expr) => ($x);// 递归处理多个参数($x:expr, $($y:expr),+) => ($x + sum!($($y),+));
}fn main() {let s1 = sum!(1, 2, 3);let s2 = sum!(10, 20, 30, 40);println!("sum1: {}, sum2: {}", s1, s2); // 输出:sum1: 6, sum2: 100
}
2. 使用std::fmt
模块处理可变参数(类似println!
)
use std::fmt;fn log(fmt: fmt::Arguments) {println!("[LOG] {}", fmt);
}fn main() {let user = "admin";let action = "login";// 使用format_args!创建可变参数log(format_args!("用户 {} 执行了 {} 操作", user, action));// 输出:[LOG] 用户 admin 执行了 login 操作
}
4.4 递归函数
递归函数是调用自身的函数,在 Rust 中使用递归需要注意:
- 必须有明确的终止条件
- Rust 目前不支持尾递归优化,递归深度过大会导致栈溢出
// 计算阶乘(n! = n × (n-1) × ... × 1)
fn factorial(n: u64) -> u64 {// 终止条件if n <= 0 {1} else {n * factorial(n - 1) // 递归调用}
}// 斐波那契数列(F(n) = F(n-1) + F(n-2))
fn fibonacci(n: u32) -> u32 {match n {0 => 0,1 => 1,_ => fibonacci(n - 1) + fibonacci(n - 2), // 递归调用}
}fn main() {println!("5的阶乘: {}", factorial(5)); // 输出:5的阶乘: 120println!("斐波那契数列第10项: {}", fibonacci(10)); // 输出:55
}
对于深度较大的递归,建议使用循环替代,避免栈溢出:
// 用循环实现阶乘(更安全)
fn factorial_iterative(n: u64) -> u64 {let mut result = 1;for i in 1..=n {result *= i;}result
}fn main() {println!("5的阶乘: {}", factorial_iterative(5)); // 输出:5的阶乘: 120
}
五、代码块与作用域({} 作用域、变量遮蔽 Shadowing)
Rust 通过代码块(block)和作用域(scope)管理变量的生命周期,变量遮蔽是 Rust 中一个有特色的概念。
5.1 代码块与作用域
代码块用{}
包围,创建一个新的作用域。变量在声明的作用域内有效,出了作用域会被自动释放(RAII 机制)。
fn main() {// 外部作用域let x = 10;println!("外部作用域的x: {}", x); // 输出:外部作用域的x: 10{// 内部作用域let y = 20;println!("内部作用域的y: {}", y); // 输出:内部作用域的y: 20println!("内部作用域可以访问外部变量x: {}", x); // 输出:10}// 错误:y的作用域已结束// println!("外部作用域无法访问y: {}", y); // 编译错误// 控制流结构(if/loop/for等)也会创建作用域if x > 5 {let z = 30;println!("if块内的z: {}", z); // 输出:if块内的z: 30}// 错误:z的作用域已结束// println!("if块外无法访问z: {}", z); // 编译错误
}
5.2 变量遮蔽(Shadowing)
变量遮蔽允许在同一作用域或内层作用域中用相同名称声明新变量,新变量会 "遮蔽" 旧变量。
fn main() {let x = 5;println!("x = {}", x); // 输出:x = 5// 遮蔽:同一作用域中创建同名新变量let x = x + 1;println!("x = {}", x); // 输出:x = 6// 遮蔽:改变变量类型let x = "现在我是字符串";println!("x = {}", x); // 输出:x = 现在我是字符串// 内层作用域遮蔽外层变量{let x = 100;println!("内层作用域的x = {}", x); // 输出:内层作用域的x = 100}// 内层作用域结束后,外层变量恢复可见println!("外层作用域的x = {}", x); // 输出:外层作用域的x = 现在我是字符串
}
变量遮蔽的主要用途:
- 转换变量类型同时保持名称一致
- 临时修改变量值但不影响外部作用域
- 简化代码(无需为相关变量创建不同名称)