Rust 中的 Result 与 Option
- 3 minutes read - 528 words在 Rust 中有两个常用的 enum 枚举类型,分别为 Result 和 Option,本节介绍它们两者各自的使用场景和用法。
这里我们先给出结论
- 结果 Result表示成功或失败
- 选项 Option表示有或者无
当从本地读取一个文件时,这时候可能读取成功,也有可能由于文件不存在或权限不足导致读取时候,这种场景一般就需要使用 Result;而当从一组数据集中查询指定元素是否存在时,这时有可能存在,也有可能不存在(用None 表示),这时情况就应该选择Option。
由此看到,这两个枚举类型的区别理解起来还是挺简单的,下面我们单独对每一种类型做一下详细的介绍。
结果 Result
定义
enum Result<T, E> {
    Ok(T),
    Err(E),
}
Result<T, E> 类型拥有两个取值:
- Ok(value)表示操作成功,并包装操作返回的- value(- value拥有- T泛类型)。
- Err(why),表示操作失败,并包装- why,它(但愿)能够解释失败的原因(- why拥有- E类型)。
举个例子,这里打开一个文件,如果文件存在则打印文件句柄信息,否则打印错误信息,查看 playground
use std::fs::File;
fn main() {
    let greeting_file_result = File::open("hello.txt");  // 返回 Result<T, E>
    let greeting_file = match greeting_file_result {
        Ok(file) => file,
        Err(error) => panic!("Problem opening the file: {:?}", error),
    };
    println!("{:?}", greeting_file);
}
这里File::open 的返回值是 Result<T, E>。泛型参数 T 会被 File::open 的实现放入成功返回值的类型 std::fs::File,这是一个文件句柄。错误返回值使用的 E 的类型是 std::io::Error。这些返回类型意味着 File::open 调用可能成功并返回一个可以读写的文件句柄。
对于结果 Result<T, E>我们一般是通过 match 来根据不同情况进行处理。
如果文件存在,则运行结果
   Compiling my_crate_demo v0.1.2 (/Users/sxf/workspace/rust/hello)
    Finished dev [unoptimized + debuginfo] target(s) in 0.87s
     Running `target/debug/my_crate_demo`
File { fd: 3, path: "/Users/sxf/workspace/rust/hello/hello.txt", read: true, write: false }
这里打印了一些文件句柄相关信息。
当文件不存在,或者权限不足导致打开文件失败
Compiling my_crate_demo v0.1.2 (/Users/sxf/workspace/rust/hello)
    Finished dev [unoptimized + debuginfo] target(s) in 0.52s
     Running `target/debug/my_crate_demo`
thread 'main' panicked at src/main.rs:8:23:
Problem opening the file: Os { code: 2, kind: NotFound, message: "No such file or directory" }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
可以看到打印的错误信息提示No such file or directory,告诉我们这里错误是由于文件未找到引起的,而非权限原因。
以上是系统内置的Result定义,我们也可以给其起一个自定义别名,如
type MyResult = Result<u32, String>;
另晚通过 type 关键字来声明,这里 MyResult 对应的正是 Result<u32,String> ,即成功时返回的是u32类型,失败时返回String错误字符串。
type MyResult = Result<u32, String>;
fn div(x: u32, y: u32) -> MyResult {
    if y == 0 {
        Err(String::from("error"))
    } else {
        Ok(x / y)
    }
}
fn main() {
    // ok
    let x = div(10, 2);
    match x {
        Ok(num) => {
            println!("val={}", num);
        }
        Err(err) => println!("{:?}", err),
    };
    // error
    let x = div(10, 0);
    match x {
        Ok(num) => {
            println!("val={}", num);
        }
        Err(err) => println!("{}", err),
    };
}
运行结果为
val=5
error
这里直接通过函数 Ok(T) 表示成功返回值,通过 Err(E) 表示失败返回值,这两个函数经常以后会经常的用到。
选项 Option
定义
pub enum Option<T> {
    None,
    Some(T),
}
它表现为以下两个 “option”(选项)中的一个:
- Some(T):找到一个属于- T类型的元素
- None:找不到相应元素
示例,声明一个Option类型变量,并读取打印其值
fn main() {
    // 定义一个 Some(T) 类型的Option, 变量类型默认后面赋值推理出来
    let num = Some(5);
    match num {
        Some(val) => println!("val={}", val),
        None => println!("None"),
    }
    // 定义一个 None 类型的 Option, 手动指定类型,否则 match 里的 Some(T) 条件则无法编译通过
    let n: Option<u32> = Option::None;
    match n {
        Some(val) => println!("val={}", val),
        None => println!("None"),
    }
}
这里我们分别针对Option枚举值 Some(T) 和 None 两类值,根据match条件进行了处理,
这里的 Some(val) 用法,表示匹配的值赋值给了变量val,因为我们需要将其打印出来。如果我们不需要这个值的话,则可以通过 Some(_) 的方法将其舍弃掉。
有一点需要注意,对于match 匹配是穷尽的,其分支必须覆盖了所有的可能性,如
fn main() {
    // 未考虑 None 的情况
    let num = Some(5);
    match num {
        Some(val) => println!("val={}", val),
        // None => println!("None"),
    }
}
此时编译出错
    Blocking waiting for file lock on build directory
   Compiling my_crate_demo v0.1.2 (/Users/sxf/workspace/rust/hello)
error[E0004]: non-exhaustive patterns: `None` not covered
   --> src/main.rs:4:11
    |
4   |     match num {
    |           ^^^ pattern `None` not covered
错误给出提示未覆盖到None的情况。
另外在满足match分支的情况下,还可以添加更多的匹配条件,如针对特定的值做特别的处理
fn main() {
    let num = Some(0);
    match num {
        Some(0) => println!("zero"),
        Some(val) => println!("val={}", val),
        None => println!("None"),
    }
}
这里变量 num 的值为 Some(0),因此匹配到第一个分支,打印 zero; 如果将变量值改为Some(2)的话,则将匹配第二个分支,打印 val=2,同样如果将变量值改为 Option::None,则匹配到第三个分支,打印None。
除此之外,还可以匹配一段区间值,如
fn main() {
    let number = Some(5);
    match number {
        Some(n) if (1..=9).contains(&n) => println!("The number is in the range 1 to 9"),
        Some(_) => println!("The number is not in the range 1 to 9"),
        None => println!("There is no number"),
    }
}
此时,将匹配到第一个分支,打印 The number is in the range 1 to 9, 这里的 if (1..=9) 后面的等号表示包含数字9。如果变量number大于5,则匹配第二个条件,这里我们将匹配的值通过 Some(_) 的分支舍弃。
以上是针对 Option类型 match 的几种常见用法, 更多 用法参考 https://kaisery.github.io/trpl-zh-cn/ch06-02-match.html
总结
针对枚举类型 Result 和 Option 的处理,需要用到 match 控制流运算符, 其用法也是比较的灵活。使用过程中我们只需要记住一点,对于 match 的匹配是无穷尽的,需要保证考虑到所有的匹配情况。
参考资料
- https://rustwiki.org/zh-CN/rust-by-example/std/result.html
- https://rustwiki.org/zh-CN/rust-by-example/std/option.html
- https://kaisery.github.io/trpl-zh-cn/ch06-02-match.html
- https://kaisery.github.io/trpl-zh-cn/ch09-02-recoverable-errors-with-result.html