我必须立刻押注 Rust 之七🎲:Option、Result 强大且优雅的控制流

859 阅读7分钟

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 上。

  • x6..=10 范围内时,x 会捕获这个匹配的具体值,然后可以在右侧的代码块中使用它。

  • println!("6 to 10 num = {x}") 是在匹配和条件成立时执行的代码。

6..=10 区间语法 ,从 6 开始到 10,包含 10(闭区间)。

if let

if let 是 Rust 中的一种控制流结构,它用于简化处理匹配单个模式的情况。

通常情况下,match 表达式用于多模式分支匹配。

if letmatch 的简化版本,主要用于当你只关心一个模式时的情况。

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

unwrapexpectOptionResult 类型上的方法。

它们都是为了从这些类型中提取内部的值,但在处理错误(即 NoneErr)的方式上有所不同。

方法用途行为
unwrapOptionResult 中提取值,如果是 NoneErr,会导致程序崩溃对于 Option,如果是 None,会 panic,输出默认错误信息;对于 Result,如果是 Err,会 panic,输出错误信息
expectunwrap 类似,但允许自定义错误信息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 中有一种简洁且强大的错误处理机制 —— ? 运算符,可简化从函数中传播错误的过程。

它可以应用于返回 ResultOption 类型的函数中,以避免大量的 match 语句或手动调用 unwrapexpect

上述 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)。

这个特性定义了 ? 运算符如何处理一个类型。

理论上,任何实现了 Try trait 的类型都可以通过 ? 运算符进行处理。

  • Option<T> 表示一个可能存在或不存在的值
  • Result<T, E> 用于错误处理,表示一个可能成功(Ok(T)) 或失败(Err(E)) 的操作
  • match 是一个强大的控制流工具,可以用于模式匹配并返回值
  • if letwhile let 提供了简化的模式匹配方式
  • unwrapexpect 是用于从 OptionResult 中提取值的方法
  • ? 运算符用于简化错误传播,自动处理 ResultOption 类型的返回值