第3篇:Rust函数与流程控制------构建逻辑清晰的系统级程序

一、学习目标与重点
1.1 学习目标
- 掌握函数定义与调用:理解Rust函数的完整语法,包括参数传递、返回值类型(单值、元组、Option/Result)
- 精通流程控制语句:熟练运用条件判断(if/else、match)和循环(loop、while、for),掌握其高级用法
- 理解模式匹配:深入学习match表达式的模式匹配语法,包括常量匹配、变量绑定、通配符等
- 实战流程控制:结合真实场景编写简单但实用的代码,如计算器、猜数字游戏、待办事项管理系统
- 优化代码逻辑:通过函数抽象和流程控制优化代码的可读性和可维护性
1.2 学习重点
💡 三大核心难点:
- match表达式的模式匹配规则(包括嵌套匹配、守卫条件)
- 函数的返回值类型推断 与早期返回(?运算符)
- 循环的标签语法 与break/continue的精确控制
⚠️ 三大高频错误点:
- match表达式的完整性检查失败(缺少匹配分支)
- if/else表达式的返回值类型不一致(Rust的if/else是表达式,可以赋值给变量)
- 函数参数的所有权转移 与引用传递混淆
二、Rust函数详解
2.1 函数的基本定义与调用
Rust的函数定义必须使用fn关键字,函数名遵循蛇形命名法(小写字母+下划线),参数和返回值类型必须明确(除了省略单位类型())。
⌨️ 函数基本定义与调用示例:
rust
// 简单函数(无参数,无返回值)
fn say_hello() {
println!("Hello, Rust!");
}
// 有参数的函数(参数类型明确)
fn add(x: i32, y: i32) -> i32 { // -> i32表示返回值类型是i32
x + y // 没有分号,表明这是表达式,返回结果
}
// 有参数的函数(带分号的语句)
fn subtract(x: i32, y: i32) -> i32 {
let result = x - y;
result // 必须是表达式,没有分号
}
fn main() {
// 调用无参数函数
say_hello();
// 调用有参数函数
let sum = add(10, 5);
println!("10 + 5 = {}", sum); // 15
let difference = subtract(10, 5);
println!("10 - 5 = {}", difference); // 5
}
2.2 函数的参数传递
Rust的函数参数传递有三种方式:
- 所有权转移:参数直接获取值的所有权,调用后原变量无法访问
- 不可变引用传递:参数获取值的不可变引用,调用后原变量仍可访问(最常用)
- 可变引用传递:参数获取值的可变引用,调用后原变量可以被修改
⌨️ 参数传递方式示例:
rust
// 所有权转移
fn takes_ownership(s: String) {
println!("takes_ownership:{}", s);
}
// 不可变引用传递
fn borrows_ownership(s: &String) {
println!("borrows_ownership:{}", s);
}
// 可变引用传递
fn mutates_ownership(s: &mut String) {
s.push_str(", Rust!");
}
fn main() {
// 所有权转移
let s1 = String::from("Hello");
takes_ownership(s1);
// println!("s1:{}", s1); // 编译错误:s1的所有权已转移
// 不可变引用传递
let s2 = String::from("World");
borrows_ownership(&s2);
println!("s2:{}", s2); // World(所有权未转移)
// 可变引用传递
let mut s3 = String::from("Rust");
mutates_ownership(&mut s3);
println!("s3:{}", s3); // Rust, Rust!(值被修改)
}
2.3 函数的返回值类型
Rust的函数返回值类型非常灵活,包括:
- 单值返回:返回一个基本类型或自定义类型的值
- 元组返回:返回多个值的组合(用于需要返回多个结果的场景)
- Option返回:表示可能返回值的场景(Some(T)表示有值,None表示无值)
- Result<T, E>返回:表示可能返回值或错误的场景(Ok(T)表示成功,Err(E)表示失败)
⌨️ 函数返回值类型示例:
rust
// 单值返回(基本类型)
fn square(x: i32) -> i32 {
x * x
}
// 单值返回(自定义类型)
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn get_origin() -> Point {
Point { x: 0, y: 0 }
}
// 元组返回(多个值的组合)
fn calculate_rectangle(width: u32, height: u32) -> (u32, u32) {
let area = width * height;
let perimeter = (width + height) * 2;
(area, perimeter)
}
// Option<T>返回(可能返回值)
fn find_max(numbers: &[i32]) -> Option<i32> {
if numbers.is_empty() {
None
} else {
let mut max = numbers[0];
for &num in numbers.iter() {
if num > max {
max = num;
}
}
Some(max)
}
}
// Result<T, E>返回(可能返回值或错误)
fn divide(x: i32, y: i32) -> Result<i32, String> {
if y == 0 {
Err(String::from("除数不能为0"))
} else {
Ok(x / y)
}
}
fn main() {
// 单值返回(基本类型)
let square_result = square(5);
println!("5的平方:{}", square_result); // 25
// 单值返回(自定义类型)
let origin = get_origin();
println!("原点坐标:{:?}", origin); // Point { x: 0, y: 0 }
// 元组返回(多个值的组合)
let (area, perimeter) = calculate_rectangle(10, 5);
println!("面积:{},周长:{}", area, perimeter); // 面积:50,周长:30
// Option<T>返回(可能返回值)
let numbers1 = [1, 3, 5, 7, 9];
if let Some(max) = find_max(&numbers1) {
println!("numbers1的最大值:{}", max); // 9
} else {
println!("numbers1是空数组");
}
let numbers2: [i32; 0] = [];
if let Some(max) = find_max(&numbers2) {
println!("numbers2的最大值:{}", max);
} else {
println!("numbers2是空数组"); // 会执行这条
}
// Result<T, E>返回(可能返回值或错误)
match divide(10, 2) {
Ok(result) => println!("10 / 2 = {}", result), // 5
Err(e) => println!("错误:{}", e),
}
match divide(10, 0) {
Ok(result) => println!("10 / 0 = {}", result),
Err(e) => println!("错误:{}", e), // 会执行这条
}
}
2.4 函数的高级用法
2.4.1 函数作为参数
Rust支持函数作为参数 ,可以实现函数式编程的特性。
⌨️ 函数作为参数示例:
rust
// 定义一个接受函数作为参数的函数
fn apply_function<F>(x: i32, f: F) -> i32
where
F: Fn(i32) -> i32, // 泛型约束:F必须是一个接受i32参数并返回i32的函数
{
f(x)
}
// 定义几个简单的函数
fn double(x: i32) -> i32 {
x * 2
}
fn triple(x: i32) -> i32 {
x * 3
}
fn square(x: i32) -> i32 {
x * x
}
fn main() {
let x = 5;
let double_result = apply_function(x, double);
println!("{}的两倍:{}", x, double_result); // 10
let triple_result = apply_function(x, triple);
println!("{}的三倍:{}", x, triple_result); // 15
let square_result = apply_function(x, square);
println!("{}的平方:{}", x, square_result); // 25
}
2.4.2 函数作为返回值
Rust支持函数作为返回值 ,可以实现闭包捕获环境变量的特性。
⌨️ 函数作为返回值示例:
rust
// 定义一个返回函数的函数
fn get_multiplier(factor: i32) -> impl Fn(i32) -> i32 {
move |x| x * factor // 使用move关键字捕获factor
}
fn main() {
let double = get_multiplier(2);
let triple = get_multiplier(3);
println!("5的两倍:{}", double(5)); // 10
println!("5的三倍:{}", triple(5)); // 15
}
三、Rust流程控制详解
3.1 条件判断
3.1.1 if/else语句
if/else语句是Rust中最基本的条件判断语句,条件表达式必须是bool类型(Rust不支持非bool类型的条件判断)。
⌨️ if/else语句示例:
rust
// 简单的if/else语句
let x = 5;
if x > 0 {
println!("x是正数");
} else if x < 0 {
println!("x是负数");
} else {
println!("x是0");
}
// if/else作为表达式(可以赋值给变量)
let y = 10;
let result = if y > 5 {
"y大于5" // 表达式,没有分号
} else {
"y小于等于5"
};
println!("{}", result); // y大于5
// 复合条件判断
let a = 10;
let b = 20;
if a > 0 && b > 0 {
println!("a和b都是正数");
} else if a < 0 || b < 0 {
println!("a或b是负数");
} else {
println!("a或b是0");
}
⚠️ if/else表达式的返回值类型 :
if/else作为表达式时,所有分支的返回值类型必须一致,否则会导致编译错误。
⌨️ if/else表达式返回值类型不一致的错误示例:
rust
let x = 5;
let result = if x > 0 {
"正数" // 类型是&str
} else {
0 // 类型是i32
};
// 编译错误:if and else have incompatible types
3.1.2 match表达式
match表达式是Rust中最强大的条件判断语句,支持模式匹配,可以匹配各种类型的值,包括整数、字符串、枚举、结构体等。
⌨️ match表达式基本示例:
rust
// 匹配整数
let x = 5;
match x {
1 => println!("x是1"),
2 => println!("x是2"),
3 | 4 => println!("x是3或4"), // 多模式匹配
5..=10 => println!("x在5-10之间"), // 范围匹配
_ => println!("x是其他值"), // 通配符匹配(必须放在最后)
}
// 匹配字符串
let s = "Hello";
match s {
"Hello" => println!("你好"),
"World" => println!("世界"),
_ => println!("其他字符串"),
}
// 匹配枚举
#[derive(Debug)]
enum Direction {
Up,
Down,
Left,
Right,
}
let direction = Direction::Right;
match direction {
Direction::Up => println!("向上"),
Direction::Down => println!("向下"),
Direction::Left => println!("向左"),
Direction::Right => println!("向右"), // 没有通配符,因为枚举的所有值都已匹配
}
⌨️ match表达式的高级用法示例:
rust
// 变量绑定
let numbers = (1, 2, 3);
match numbers {
(x, y, z) if x + y > z => println!("x + y > z:{} + {} > {}", x, y, z), // 使用守卫条件
(x, y, z) => println!("x + y <= z:{} + {} <= {}", x, y, z),
}
// 嵌套匹配
#[derive(Debug)]
enum Action {
Move { x: i32, y: i32 },
Jump(i32),
Attack,
}
let action = Action::Move { x: 10, y: 5 };
match action {
Action::Move { x, y } => println!("移动到({}, {})", x, y),
Action::Jump(height) => println!("跳跃{}个单位", height),
Action::Attack => println!("攻击"),
}
3.2 循环
3.2.1 loop循环
loop循环是Rust中最简单的循环语句,无限循环,直到遇到break关键字。
⌨️ loop循环示例:
rust
// 简单的loop循环(计算1-10的和)
let mut sum = 0;
let mut i = 1;
loop {
sum += i;
i += 1;
if i > 10 {
break; // 跳出循环
}
}
println!("1-10的和:{}", sum); // 55
// loop循环作为表达式(可以返回值)
let mut x = 0;
let result = loop {
x += 1;
if x == 5 {
break x * 2; // 跳出循环并返回x * 2
}
};
println!("loop循环返回值:{}", result); // 10
3.2.2 while循环
while循环是Rust中最常用的循环语句,条件判断在循环开始前,如果条件为true,则执行循环体,否则跳出循环。
⌨️ while循环示例:
rust
// 简单的while循环(计算1-10的和)
let mut sum = 0;
let mut i = 1;
while i <= 10 {
sum += i;
i += 1;
}
println!("1-10的和:{}", sum); // 55
// 使用while循环遍历数组
let mut numbers = [1, 3, 5, 7, 9];
let mut index = 0;
while index < numbers.len() {
println!("{}", numbers[index]);
index += 1;
}
3.2.3 for循环
for循环是Rust中最强大的循环语句,专门用于遍历集合(数组、Vec、字符串、范围等)。
⌨️ for循环示例:
rust
// 遍历范围(1-10)
let mut sum = 0;
for i in 1..=10 {
sum += i;
}
println!("1-10的和:{}", sum); // 55
// 遍历数组
let numbers = [1, 3, 5, 7, 9];
for num in numbers.iter() {
println!("{}", num);
}
// 遍历字符串的字符
let s = "Rust语言开发";
for c in s.chars() {
println!("{}", c);
}
// 遍历Vec
let mut vec = vec![10, 20, 30, 40, 50];
for num in &mut vec {
*num *= 2; // 使用解引用运算符*修改值
}
println!("Vec的值:{:?}", vec); // [20,40,60,80,100]
3.3 循环的高级用法
3.3.1 标签语法
Rust支持标签语法,可以给循环添加标签,以便在嵌套循环中精确控制break/continue的行为。
⌨️ 标签语法示例:
rust
// 标签语法控制break
'outer: loop {
println!("外层循环开始");
'inner: loop {
println!("内层循环开始");
break 'outer; // 跳出外层循环
}
println!("外层循环结束"); // 不会执行
}
// 标签语法控制continue
'outer: for i in 1..=3 {
println!("外层循环:{}", i);
'inner: for j in 1..=3 {
if j == 2 {
continue 'outer; // 跳过外层循环的当前迭代
}
println!("内层循环:{}", j);
}
}
3.3.2 break/continue的高级用法
break关键字可以跳出循环并返回值 ,continue关键字可以跳过循环的当前迭代。
⌨️ break/continue的高级用法示例:
rust
// break跳出循环并返回值
let mut x = 0;
let result = loop {
x += 1;
if x == 5 {
break x * 2; // 跳出循环并返回x * 2
}
};
println!("loop循环返回值:{}", result); // 10
// continue跳过当前迭代
let mut numbers = [1, 3, 5, 7, 9];
for num in numbers.iter() {
if *num == 5 {
continue; // 跳过5
}
println!("{}", num); // 输出1,3,7,9
}
四、真实案例应用
4.1 案例1:简单的计算器
💡 场景分析:需要编写一个简单的计算器,支持加法、减法、乘法、除法四种运算,读取用户输入的操作符和操作数,计算并输出结果。
⌨️ 代码示例:
rust
use std::io;
// 定义操作符的枚举类型
#[derive(Debug)]
enum Operator {
Add,
Subtract,
Multiply,
Divide,
}
// 解析操作符的函数
fn parse_operator(input: &str) -> Option<Operator> {
match input.trim() {
"+" => Some(Operator::Add),
"-" => Some(Operator::Subtract),
"*" => Some(Operator::Multiply),
"/" => Some(Operator::Divide),
_ => None,
}
}
// 解析操作数的函数
fn parse_number(input: &str) -> Option<f64> {
input.trim().parse().ok()
}
// 计算结果的函数
fn calculate(op: Operator, x: f64, y: f64) -> Result<f64, String> {
match op {
Operator::Add => Ok(x + y),
Operator::Subtract => Ok(x - y),
Operator::Multiply => Ok(x * y),
Operator::Divide => {
if y == 0.0 {
Err(String::from("除数不能为0"))
} else {
Ok(x / y)
}
}
}
}
fn main() {
println!("简单的计算器");
println!("----------------");
println!("支持的操作符:+、-、*、/");
println!("请输入操作符和两个操作数(用空格分隔,例如:+ 10 5)");
loop {
let mut input = String::new();
io::stdin().read_line(&mut input).expect("读取输入失败");
let tokens: Vec<&str> = input.trim().split_whitespace().collect();
if tokens.len() != 3 {
println!("输入格式错误,请重新输入");
continue;
}
let operator = match parse_operator(tokens[0]) {
Some(op) => op,
None => {
println!("无效的操作符,请重新输入");
continue;
}
};
let x = match parse_number(tokens[1]) {
Some(num) => num,
None => {
println!("第一个操作数无效,请重新输入");
continue;
}
};
let y = match parse_number(tokens[2]) {
Some(num) => num,
None => {
println!("第二个操作数无效,请重新输入");
continue;
}
};
let result = calculate(operator, x, y);
match result {
Ok(res) => println!("结果:{:.2}", res),
Err(e) => println!("错误:{}", e),
}
println!("是否继续计算?(输入y继续,输入其他内容结束)");
let mut choice = String::new();
io::stdin().read_line(&mut choice).expect("读取输入失败");
if choice.trim().to_lowercase() != "y" {
break;
}
}
println!("计算器结束");
}
4.2 案例2:猜数字游戏
💡 场景分析:需要编写一个猜数字游戏,程序随机生成一个1-100之间的整数,用户输入猜测的数字,程序提示猜测过高或过低,直到用户猜中为止。
⌨️ 代码示例:
rust
use std::io;
use rand::Rng; // 需要在Cargo.toml中添加rand库的依赖
fn main() {
println!("猜数字游戏");
println!("----------------");
println!("程序将随机生成一个1-100之间的整数");
println!("请输入你猜测的数字");
// 生成随机数
let secret_number = rand::thread_rng().gen_range(1..=100);
loop {
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("读取输入失败");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("请输入有效的整数");
continue;
}
};
if guess < 1 || guess > 100 {
println!("猜测的数字必须在1-100之间");
continue;
}
match guess.cmp(&secret_number) {
std::cmp::Ordering::Less => println!("猜测过低"),
std::cmp::Ordering::Greater => println!("猜测过高"),
std::cmp::Ordering::Equal => {
println!("恭喜你,猜中了!");
break;
}
}
}
}
⚠️ 注意:需要在Cargo.toml中添加rand库的依赖:
toml
[dependencies]
rand = "0.8.5"
4.3 案例3:待办事项管理系统
💡 场景分析:需要编写一个简单的待办事项管理系统,支持添加待办事项、删除待办事项、查看待办事项、标记待办事项为已完成。
⌨️ 代码示例:
rust
use std::io;
use std::collections::HashMap;
#[derive(Debug, Clone)]
struct TodoItem {
id: u32,
title: String,
completed: bool,
}
struct TodoList {
items: HashMap<u32, TodoItem>,
next_id: u32,
}
impl TodoList {
// 创建新的待办事项列表
fn new() -> Self {
TodoList {
items: HashMap::new(),
next_id: 1,
}
}
// 添加待办事项
fn add_item(&mut self, title: String) {
let item = TodoItem {
id: self.next_id,
title,
completed: false,
};
self.items.insert(self.next_id, item);
self.next_id += 1;
}
// 删除待办事项
fn delete_item(&mut self, id: u32) -> bool {
self.items.remove(&id).is_some()
}
// 标记待办事项为已完成
fn mark_completed(&mut self, id: u32) -> bool {
if let Some(item) = self.items.get_mut(&id) {
item.completed = true;
true
} else {
false
}
}
// 查看待办事项
fn list_items(&self) {
if self.items.is_empty() {
println!("待办事项列表是空的");
return;
}
println!("待办事项列表");
println!("----------------");
println!("ID\t标题\t\t状态");
println!("----------------");
for (_, item) in self.items.iter() {
let status = if item.completed { "已完成" } else { "未完成" };
println!("{}\t{}\t{}", item.id, item.title, status);
}
}
}
fn main() {
let mut todo_list = TodoList::new();
loop {
println!("待办事项管理系统");
println!("----------------");
println!("1. 添加待办事项");
println!("2. 删除待办事项");
println!("3. 标记待办事项为已完成");
println!("4. 查看待办事项");
println!("5. 退出");
println!("----------------");
println!("请输入操作编号:");
let mut choice = String::new();
io::stdin().read_line(&mut choice).expect("读取输入失败");
match choice.trim().parse::<u32>() {
Ok(1) => {
println!("请输入待办事项的标题:");
let mut title = String::new();
io::stdin().read_line(&mut title).expect("读取输入失败");
todo_list.add_item(title.trim().to_string());
println!("待办事项添加成功");
}
Ok(2) => {
println!("请输入待办事项的ID:");
let mut id = String::new();
io::stdin().read_line(&mut id).expect("读取输入失败");
match id.trim().parse::<u32>() {
Ok(id) => {
if todo_list.delete_item(id) {
println!("待办事项删除成功");
} else {
println!("待办事项不存在");
}
}
Err(_) => println!("无效的ID"),
}
}
Ok(3) => {
println!("请输入待办事项的ID:");
let mut id = String::new();
io::stdin().read_line(&mut id).expect("读取输入失败");
match id.trim().parse::<u32>() {
Ok(id) => {
if todo_list.mark_completed(id) {
println!("待办事项标记为已完成");
} else {
println!("待办事项不存在");
}
}
Err(_) => println!("无效的ID"),
}
}
Ok(4) => todo_list.list_items(),
Ok(5) => {
println!("待办事项管理系统结束");
break;
}
Ok(_) => println!("无效的操作编号"),
Err(_) => println!("无效的输入"),
}
println!();
}
}
五、常见问题与解决方案
5.1 match表达式缺少匹配分支
问题现象:match表达式的所有分支没有完全覆盖所有可能的情况,导致编译错误。
解决方案:添加一个通配符匹配分支(_),覆盖所有未匹配的情况。
⌨️ 通配符匹配示例:
rust
let x = 5;
match x {
1 => println!("x是1"),
2 => println!("x是2"),
_ => println!("x是其他值"), // 通配符匹配,必须放在最后
}
5.2 if/else表达式返回值类型不一致
问题现象:if/else作为表达式时,所有分支的返回值类型不一致,导致编译错误。
解决方案:确保所有分支的返回值类型一致。
⌨️ if/else表达式返回值类型一致的示例:
rust
let x = 5;
let result = if x > 0 {
"正数" // 类型是&str
} else {
"非正数" // 类型也是&str
};
println!("{}", result); // 正数
5.3 函数参数的所有权转移与引用传递混淆
问题现象:函数参数直接获取值的所有权,调用后原变量无法访问,导致编译错误。
解决方案:使用不可变引用传递或可变引用传递,而不是所有权转移。
⌨️ 不可变引用传递示例:
rust
fn borrows_ownership(s: &String) {
println!("{}", s);
}
fn main() {
let s = String::from("Hello");
borrows_ownership(&s);
println!("{}", s); // Hello(所有权未转移)
}
5.4 循环中的变量捕获问题
问题现象:在循环中创建闭包并捕获变量时,可能会导致变量的所有权或生命周期问题。
解决方案:使用move关键字捕获变量,或确保变量的生命周期在闭包使用期间有效。
⌨️ 使用move关键字捕获变量示例:
rust
fn main() {
let mut vec = vec![10, 20, 30, 40, 50];
let mut handles = Vec::new();
for num in vec.iter() {
let handle = std::thread::spawn(move || {
println!("{}", num);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
六、总结与展望
6.1 总结
✅ 掌握了Rust函数的完整语法,包括参数传递、返回值类型(单值、元组、Option/Result)
✅ 精通了流程控制语句,包括条件判断(if/else、match)和循环(loop、while、for)
✅ 深入学习了match表达式的模式匹配语法,包括常量匹配、变量绑定、通配符等
✅ 结合真实场景编写了三个实用的代码案例,如简单的计算器、猜数字游戏、待办事项管理系统
✅ 优化了代码的逻辑,通过函数抽象和流程控制提高了代码的可读性和可维护性
6.2 展望
下一篇文章,我们将深入学习Rust的所有权、借用和生命周期,这是Rust语言的核心内存安全机制,也是最容易让新手困惑的地方。我们将详细讲解所有权的基本概念、借用的规则、生命周期的标注方法,并通过真实场景应用这些知识,编写安全、高效的代码。