Rust:函数与控制流
Rust:函数与控制流
- 条件表达式
- if分支语句
- if表达式化
- if表达式的类型要求
- match分支匹配
- match的穷尽性要求
- match的模式匹配
- 模式守卫
- match表达式返回值
- 循环控制
- loop无限循环
- break和continue
- loop表达式返回值
- while条件循环
- while循环中的break
- for迭代器循环
- for循环获取索引
- 标签跳转
- 函数签名与返回值
- 基本函数定义
- 带返回值的函数
- return
- 单元类型()
- 参数传递
- 数组参数
- 参数模式匹配
- 发散函数!
- 类型兼容性
在编程中,函数和控制流是构建程序逻辑的核心要素。Rust 在这些基础概念上引入了许多独特的设计,比如表达式化的控制结构、严格的类型系统以及所有权机制。本文将从最基础的概念开始,逐步深入探讨 Rust 中的条件表达式、循环控制、函数定义以及各种返回机制。
条件表达式
if分支语句
if
是最基本的条件控制结构,用于根据条件执行不同的代码分支:
let number = 6;if number > 5 {println!("数字大于5");
}if number % 2 == 0 {println!("数字是偶数");
} else {println!("数字是奇数");
}
这是最传统的 if
用法,根据条件执行相应的代码块。当条件为真时,执行 if
后的代码块;当条件为假且有 else
分支时,执行 else
后的代码块。
if表达式化
与其他语言不同,Rust 中的 if
不仅是语句,更是表达式,可以返回值:
let number = 6;let result = if number % 2 == 0 {"偶数"
} else {"奇数"
};println!("数字 {} 是 {}", number, result);
这里的关键概念是:代码块 {}
的最后一个表达式就是该代码块的返回值。注意 "偶数"
和 "奇数"
后面都没有分号,这意味着它们是表达式而不是语句,会作为代码块的返回值。
if表达式的类型要求
由于 if
是表达式,编译器需要在编译时确定其类型,因此所有分支必须返回相同类型:
let condition = true;// 正确:所有分支返回相同类型
let number = if condition {5
} else {6
};println!("number = {}", number);// 错误示例(会编译失败):
let mixed = if condition {5 // 整数类型
} else {"six" // 字符串类型
};
这个限制确保了类型安全,编译器可以在编译时就确定变量的类型,避免运行时的类型错误。
match分支匹配
match
是 Rust 中更强大的分支控制结构,可以匹配多种模式:
let number = 3;match number {1 => println!("一"),2 => println!("二"),3 => println!("三"),_ => println!("其他"),
}
match
通过模式匹配来决定执行哪个分支。每个分支由模式和对应的代码组成,用 =>
连接。_
是通配符,匹配所有其他情况。
match的穷尽性要求
match
必须覆盖被匹配值的所有可能情况,这被称为"穷尽性"要求:
enum Direction {North,South,East,West,
}let dir = Direction::North;match dir {Direction::North => "向北前进",Direction::South => "向南前进",Direction::East => "向东前进",Direction::West => "向西前进",// 如果缺少任何一个枚举值,编译器会报错
}
穷尽性检查是编译时进行的,确保你不会遗漏任何情况。这大大减少了运行时错误的可能性。
match的模式匹配
match
支持多种复杂的模式匹配:
let number = 7;match number {1 => println!("一"),2 | 3 => println!("二或三"), // 多值匹配4..=6 => println!("四到六"), // 范围匹配_ => println!("其他"),
}
- 多值匹配:使用
|
可以匹配多个值 - 范围匹配:使用
..=
可以匹配一个范围内的值
模式守卫
模式守卫允许在匹配模式的基础上添加额外的条件:
let number = 15;match number {n if n > 20 => println!("大于20: {}", n),n if n > 10 => println!("大于10但小于等于20: {}", n),n if n > 0 => println!("大于0但小于等于10: {}", n),0 => println!("恰好是零"),_ => println!("负数或其他"),
}
模式守卫使用 变量 if 条件
的形式,在模式匹配成功后,还要满足 if
后的条件才会执行对应分支,如果不匹配,那么向下继续匹配。
match表达式返回值
与 if
类似,match
也是表达式,可以返回值:
let number = 3;let description = match number {1 => "一",2 => "二", 3 => "三",_ => "其他",
};println!("数字 {} 对应 {}", number, description);
所有分支必须返回相同类型的值,这样整个 match
表达式才有确定的类型。
循环控制
loop无限循环
loop
创建一个无限循环,是最基本的循环结构:
let mut counter = 0;loop {counter += 1;println!("计数: {}", counter);if counter == 5 {break;}
}println!("循环结束");
loop
会无限执行,只有通过 break
才能退出循环。这是最直接的循环控制方式,适用于需要明确控制退出条件的场景。
break和continue
在循环中,break
用于退出循环,continue
用于跳过本次迭代:
let mut count = 0;loop {count += 1;if count % 2 == 0 {continue; // 跳过偶数}if count > 10 {break; // 超过10就退出}println!("奇数: {}", count);
}
continue
会跳过当前循环的剩余代码,直接进入下一次循环。break
会立即退出整个循环。
loop表达式返回值
loop
也是表达式,可以通过 break
返回值:
let mut num = 1;let result = loop {if num % 2 == 0 {break num; // 返回 num 的值}num += 1;
};println!("第一个偶数: {}", result);
这里 break num
表示退出循环并返回 num
的值。整个 loop
表达式的值就是 break
后面的值。
while条件循环
while
循环在条件为真时持续执行:
let mut number = 5;while number > 0 {println!("倒计时: {}", number);number -= 1;
}println!("发射!");
while
在每次循环开始前检查条件,条件为假时退出循环。这比 loop
+ if
+ break
的组合更简洁。
while循环中的break
while
循环中也可以使用 break
提前退出,但与 loop
不同的是,while
循环不允许 break
携带返回值:
let mut count = 0;while count < 10 {count += 1;if count == 5 {break; // 可以 break,但不能 break 值}println!("计数: {}", count);
}let result = while condition { break 42; }; // 错误!while 不能返回值
这是因为 while
循环的条件可能一开始就为假,那样循环体根本不会执行,无法确定返回值。
for迭代器循环
for
循环用于遍历集合或迭代器:
let numbers = [1, 2, 3, 4, 5];for num in numbers {println!("数字: {}", num);
}
for
循环会自动遍历可迭代对象的每个元素。这是 Rust 中最常用的循环方式,既安全又高效。
for循环获取索引
有时需要在遍历时获取元素的索引:
let fruits = ["苹果", "香蕉", "橙子"];for (index, fruit) in fruits.iter().enumerate() {println!("{}: {}", index, fruit);
}
enumerate()
方法返回 (索引, 元素)
的元组,让你可以同时访问索引和元素值。
标签跳转
当有嵌套循环时,可以使用标签来指定 break
或 continue
要影响哪个循环:
'outer: loop {println!("进入外层循环");'inner: loop {println!("进入内层循环");break 'outer; // 跳出外层循环}println!("这行代码不会执行");
}println!("退出所有循环");
标签以单引号开头,可以让 break
和 continue
作用于指定的循环。这在复杂的嵌套循环中很有用。
当使用标签跳转时,break
也可以携带返回值,但只能在 loop
循环中使用:
let result = 'outer: loop {let mut inner_count = 0;'inner: loop {inner_count += 1;println!("内层计数: {}", inner_count);if inner_count == 3 {break 'outer inner_count * 10; // 跳出外层循环并返回值}if inner_count > 5 {break 'inner; // 只跳出内层循环}}println!("这行代码不会执行");
};println!("返回值: {}", result); // 输出: 返回值: 30
标签跳转的返回值只能用于 loop
循环,while
和 for
循环即使使用标签也不能返回值。
函数签名与返回值
基本函数定义
函数是封装代码逻辑的基本单位:
fn greet(name: &str) {println!("Hello, {}!", name);
}greet("World");
函数使用 fn
关键字定义,函数名后跟参数列表,参数需要指定类型。
带返回值的函数
函数可以返回值,需要在参数列表后指定返回类型:
fn add(a: i32, b: i32) -> i32 {a + b
}let result = add(5, 3);
println!("5 + 3 = {}", result);
使用 ->
指定返回类型,函数体最后一个表达式作为返回值。
对于包含多个分支的函数,每个分支的最后一个表达式都可以作为该分支的返回值:
fn calculate(x: i32) -> i32 {let doubled = x * 2;if doubled > 10 {doubled - 5 // 这个分支的返回值} else {doubled + 5 // 这个分支的返回值}// 整个 if 表达式的值作为函数返回值
}fn classify_number(n: i32) -> &'static str {match n {0 => "零", // 这个分支的返回值1..=10 => "小数", // 这个分支的返回值11..=100 => "中数", // 这个分支的返回值_ => "大数", // 这个分支的返回值}// 整个 match 表达式的值作为函数返回值
}
函数体最后一个表达式(这里是整个 if
或 match
表达式)自动作为返回值,不需要显式的 return
。
return
虽然 Rust 支持隐式返回,但有时需要在函数中间提前返回:
fn check_positive(x: i32) -> i32 {if x <= 0 {return 0; // 提前返回}x * 2 // 正常返回
}
return
关键字可以在函数的任何位置提前返回值并退出函数。
单元类型()
当函数不需要返回有意义的值时,返回单元类型 ()
:
fn print_info(msg: &str) {println!("信息: {}", msg);// 隐式返回 ()
}fn do_something() -> () {println!("执行某些操作");// 显式指定返回 (),但通常不需要
}
单元类型 ()
表示"无意义的值",类似于其他语言中的 void
。不返回值的函数实际上返回 ()
。
参数传递
函数可以接受参数:
fn display_info(name: &str, age: i32, height: f64) {println!("姓名: {}", name);println!("年龄: {} 岁", age);println!("身高: {:.1} 米", height);
}fn calculate_area(width: f64, height: f64) -> f64 {width * height
}
函数参数按顺序传递,每个参数都必须指定类型。
数组参数
可以将数组作为参数传递:
fn print_array(arr: [i32; 5]) {for element in arr {println!("{}", element);}
}fn sum_array(arr: [i32; 3]) -> i32 {let mut total = 0;for element in arr {total += element;}total
}fn main() {let numbers = [1, 2, 3, 4, 5];print_array(numbers);let small_array = [10, 20, 30];let sum = sum_array(small_array);println!("数组总和: {}", sum);
}
数组参数需要指定确切的大小,例如 [i32; 5]
表示包含5个i32元素的数组。
参数模式匹配
函数参数可以使用模式匹配直接解构:
// 解构元组参数
fn print_point((x, y): (i32, i32)) {println!("坐标: ({}, {})", x, y);
}// 解构数组的前几个元素
fn print_first_two([first, second, ..]: [i32; 5]) {println!("前两个元素: {} 和 {}", first, second);
}
参数模式匹配让你可以直接在函数签名中解构复杂类型,使代码更简洁。
发散函数!
发散函数是永远不会正常返回的函数,返回类型是 !
:
fn crash() -> ! {panic!("程序崩溃了");
}fn infinite_loop() -> ! {loop {println!("永远运行...");}
}
!
类型表示"never",意味着函数永远不会正常返回。常见的发散函数包括 panic!
、无限循环等。
类型兼容性
一般来说,我们不会把!
直接作为返回值,这个类型的特殊之处在于它可以强制转换为任何类型:
fn safe_divide(a: i32, b: i32) -> i32 {if b == 0 {handle_error("除零错误"); // ! 类型,但兼容 i32} else {a / b // i32 类型}// 整个 if 表达式的类型是 i32
}
!
类型可以与任何类型兼容,这使得在某些分支中使用 panic!
或 exit
变得很自然,哪怕函数要求返回 i32
等其他类型,你也可以返回一个 !
来表示一个错误。