序
对 rust 的探索,源于一段惊艳到我的代码
fn fibonacci(n: u32) -> u32 {
match n {
1 | 2 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
对于习惯了 if...else 的我而言,斐波那契数列原来可以这样写,我感到十分吃惊。
本篇是 我必须立刻押注 Rust 的第七章。一起来看 Rust 优雅的控制流(control flow)!
Option 类型
Option 用来表示一个值要么存在,要么不存在(类似于其他语言的 null 或 undefined)
它定义在 Rust 标准库中:
enum Option<T> {
Some(T),
None,
}
示例:从数组中获取一个可能不存在的元素
let numbers = [10, 20, 30];
let first = numbers.get(0); // 返回 Option 类型
let fourth = numbers.get(3);
match first {
Some(value) => println!("First element: {}", value),
None => println!("No element found"),
}
match fourth {
Some(value) => println!("Fourth element: {}", value),
None => println!("No element found"),
}
let numbers = [10, 20, 30, 40, 50];
let n = numbers.get(2);
let n2 = numbers.get(4);
match (n, n2) { // 匹配多个条件
(Some(value1), Some(value2)) => println!(
"Both values are present: {} and {}", value1, value2
),
(Some(_), None) => println!("Only the first value is present"),
(None, Some(_)) => println!("Only the second value is present"),
(None, None) => println!("Neither value is present"),
}
Result 类型
Result 用于处理可能的错误情况,定义如下:
enum Result<T, E> {
Ok(T),
Err(E),
}
示例:从字符串转换整数,处理可能的错误。
fn main() {
let number_str = "42";
let invalid_str = "not a number";
let number: Result<i32, _> = number_str.parse();
let invalid: Result<i32, _> = invalid_str.parse();
match number {
Ok(n) => println!("Parsed number: {}", n),
Err(e) => println!("Failed to parse: {:?}", e),
}
match invalid {
Ok(n) => println!("Parsed number: {}", n),
Err(e) => println!("Failed to parse: {:?}", e),
}
}
match
let a_number = Option::Some(10);
match a_number {
Some(x) if x <= 5 => println!("0 to 5 num = {x}"),
Some(x @ 6..=10) => println!("6 to 10 num = {x}"),
None => {},
// all other numbers
_ => {},
}
// match 不仅用于控制流判断,还会返回一个值。
// 因为它是一个表达式,所以你可以直接将 match 表达式的结果赋值给一个变量。
let a = match a_number {
Some(x) => x,
None => 0
}
条件守卫
Some(x) if x <= 5 => println!("0 to 5 num = {x}"),
这是 Rust 模式匹配中的条件守卫(if guard)语法。
条件守卫允许我们在模式匹配时添加额外的布尔条件,以进一步约束匹配条件。
在这个例子中:
Some(x)用于解构Option枚举类型,并将其中的值绑定到变量x上。if x <= 5是条件守卫,表示仅当x小于或等于 5 时,才会执行该分支的代码。println!("0 to 5 num = {x}")是在匹配和条件成立时执行的代码。
绑定模式
Some(x @ 6..=10) => println!("6 to 10 num = {x}")
这是 Rust 模式匹配中的绑定模式(x @ pattern)语法。
绑定模式是 Rust 中的一个强大匹配特性,它允许我们在模式匹配的同时,将匹配的值绑定到一个变量上,从而在后续的代码中直接使用这个值。
在这个例子中:
-
x @ 6..=10是绑定模式(@操作符),它允许我们将模式匹配成功的值绑定到变量x上。 -
当
x在6..=10范围内时,x会捕获这个匹配的具体值,然后可以在右侧的代码块中使用它。 -
println!("6 to 10 num = {x}")是在匹配和条件成立时执行的代码。
6..=10区间语法 ,从6开始到10,包含10(闭区间)。
if let
if let 是 Rust 中的一种控制流结构,它用于简化处理匹配单个模式的情况。
通常情况下,match 表达式用于多模式分支匹配。
而 if let 是 match 的简化版本,主要用于当你只关心一个模式时的情况。
let some_value = Some(10);
if let Some(x) = some_value {
println!("The value is: {x}");
} else {
println!("It's None");
// 对于 Result 类型,我们可能只对 Ok 感兴趣
if let Ok(value) = result {
println!("Success with value: {value}");
}
while let
while let 是 Rust 中另一种流控制结构,适用于在满足特定条件时进行循环操作。
它和 if let 类似,但 while let 会在条件持续为 true 的情况下不断执行循环,直到条件不再满足时结束。
这样就无需显式地控制退出条件,非常适合处理迭代器或循环读取可选值的场景。
示例一
let binding = [Some(1), Some(2), None, Some(3)];
let mut values = binding.iter();
// 一旦遇到 None , 循环结束
while let Some(Some(x)) = values.next() {
println!("Processing value: {}", x); // 1 ; 2
}
示例二
逐行读取用户终端输入的数据,并在每次成功读取后,将读取到的行打印到标准输出。
// use std::io::{self, BufRead};
let stdin = io::stdin();
let mut lines = stdin.lock().lines();
while let Some(Ok(line)) = lines.next() {
println!("Read line: {}", line);
}
unwrap 与 expect
unwrap 和 expect 是 Option 和 Result 类型上的方法。
它们都是为了从这些类型中提取内部的值,但在处理错误(即 None 或 Err)的方式上有所不同。
| 方法 | 用途 | 行为 |
|---|---|---|
| unwrap | 从 Option 或 Result 中提取值,如果是 None 或 Err,会导致程序崩溃 | 对于 Option,如果是 None,会 panic,输出默认错误信息;对于 Result,如果是 Err,会 panic,输出错误信息 |
| expect | 与 unwrap 类似,但允许自定义错误信息 | 与 unwrap 类似,但在 panic 时,会输出自定义的错误信息 |
panic! 在程序运行时发生错误时,引发 panic 并终止程序运行。
示例一
let some_option = Some(5);
let value = some_option.unwrap(); // 返回 5
println!("[expect_unwrap] {}", value);
let some_option:Option<i32> = None;
let value = some_option.unwrap();
// 导致 panic, 系统默认错误信息 called `Option::unwrap()` on a `None` value
println!("[expect_unwrap] {}", value);
示例二
let some_result:Result<i32, &str> = Ok(10);
let value = some_result.expect("Failed to get a value"); // 返回 10
println!("[expect_unwrap] {}", value);
let err_result: Result<i32, &str> = Err("Something went wrong");
let value = err_result.expect("Failed to get a value"); // 导致 panic,输出 "Failed to get a value"
println!("[expect_unwrap] {}", value);
传播错误
match Err()
根据我们之前所学,写一个从文件中读取字符串的示例,如下:
pub fn read_username_from_file () -> Result<String, Error> {
let full_path = "username.txt";
let mut file = match File::open(full_path) {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut username = String::new();
match file.read_to_string(&mut username) {
Ok(_) => Ok(username),
Err(err) => Err(err)
}
}
match 中的 Err(err) => return Err(err) ,是希望将 File::open(full_path) 结果中的错误,原原本本从 read_username_from_file 函数中返回。
同理, 末尾 Err(err) => Err(err) 也是将 read_to_string 中的错误从函数返回。
Err(err) => return Err(err)由于
return语句会立即终止函数read_username_from_file执行,Err(e)将作为整个函数的返回值,因此不会有值被赋给let mut file。
? 运算符
Rust 中有一种简洁且强大的错误处理机制 —— ? 运算符,可简化从函数中传播错误的过程。
它可以应用于返回 Result 或 Option 类型的函数中,以避免大量的 match 语句或手动调用 unwrap 和 expect。
上述 read_username_from_file 可以被简化为:
pub fn read_username_from_file () -> Result<String, Error> {
let full_path = "username.txt";
let mut file = File::open(full_path)?;
let mut username = String::new();
file.read_to_string(&mut username)?;
Ok(username)
}
?运算符会为我们自动处理match` 操作。- 如果结果是
Ok,它会自动继续并返回值。 - 如果结果是
Err,它会自动从当前函数返回该错误。
同 Result 类似,在 Option 中
pub fn get_first_char(s: &str) -> Option<char> {
let first_char = s.chars().next()?; // `?` 在这里用于处理 Option
Some(first_char)
}
- 如果结果是
Some,它会自动继续并返回值。 - 如果结果是
None,它会自动从当前函数返回None。
Rust 的
?运算符实际上是依赖于一个叫做Try的内部特性(trait)。这个特性定义了
?运算符如何处理一个类型。理论上,任何实现了
Trytrait 的类型都可以通过?运算符进行处理。
结
Option<T>表示一个可能存在或不存在的值Result<T, E>用于错误处理,表示一个可能成功(Ok(T)) 或失败(Err(E)) 的操作match是一个强大的控制流工具,可以用于模式匹配并返回值if let和while let提供了简化的模式匹配方式unwrap和expect是用于从Option和Result中提取值的方法?运算符用于简化错误传播,自动处理Result或Option类型的返回值