使用 `Result` 进行健壮的错误处理
使用 `Result` 枚举、`?` 运算符和自定义错误类型实现符合 Rust 惯用方式的错误处理,构建更具韧性的应用。
使用 `Result` 进行健壮的错误处理 是 CoddyKit 上的免费 Learn Rust Coding 课时。 这是第 3 节课,共 3 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Learn Rust Coding 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Learn Rust Coding 课程共包含 3 节课。
处理 Rust 中的错误
错误是编程的一部分。在 Rust 中,我们将错误分为两种主要类型:可恢复错误和不可恢复错误。
- 不可恢复错误通常表示程序中存在错误,并通过
panic!使程序停止。 - 可恢复错误表示某些操作出现了问题,但可以进行处理,例如找不到文件。
Rust 可以帮助您优雅地处理可恢复错误。
`panic!` 与 `Result`
发生不可恢复错误时,Rust 会调用 panic! 宏。它会展开栈并退出程序,通常还会打印错误消息。
对于可恢复错误,Rust 使用 Result 枚举。这个枚举会告诉您操作是成功了(Ok)还是失败了(Err),从而让程序决定接下来要执行的操作。
`Result` 枚举
Result 枚举在概念上定义如下:
enum Result<T, E> {
Ok(T),
Err(E),
}
T表示成功时返回的值的类型。E表示失败时返回的错误类型。
这是一种明确说明函数可能失败的强大方式。
匹配 `Result`
处理 Result 最常见的方式是使用 match 表达式。这样,您就可以根据 Result 是 Ok 还是 Err 来执行不同的代码块。
请尝试运行下面这个解析数字的示例:
fn parse_and_add(text: &str) -> Result<i32, std::num::ParseIntError> {
text.parse::<i32>()
}
fn main() {
let num_str = "123";
let result = parse_and_add(num_str);
match result {
Ok(num) => println!("Parsed number: {}", num),
Err(e) => println!("Error parsing: {}", e),
}
let bad_str = "abc";
let bad_result = parse_and_add(bad_str);
match bad_result {
Ok(num) => println!("Parsed number: {}", num),
Err(e) => println!("Error parsing: {}", e),
}
}解包结果(请谨慎操作!)
有时您可能确定某个操作不会失败,或者希望操作失败时程序直接崩溃。unwrap() 和 expect() 等方法可以提取 Ok 中的值。
unwrap():返回Ok中的值;如果是Err,则触发恐慌。expect("message"):作用与unwrap()相同,但允许您提供自定义的恐慌消息。
请谨慎使用这些方法,主要在测试中使用,或仅在失败确实不可恢复时使用。
介绍 `?` 运算符
为每个 Result 编写 match 语句可能会很重复。? 运算符为传播错误提供了一种简洁的快捷方式。
将 ? 放在返回 Result 的表达式后面时,它会执行两项操作:
- 如果
Result是Err,它会立即从当前函数返回该Err。 - 如果
Result是Ok,它会解包Ok中的值并继续执行。
`?` 运算符实战
? 运算符让可能失败的函数更易于编写。请记住,使用 ? 的函数本身必须返回 Result(或 Option)。
请观察在这个读取文件的示例中,? 如何简化错误处理:
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?; // Propagates error if file not found
let mut s = String::new();
f.read_to_string(&mut s)?; // Propagates error if read fails
Ok(s)
}
fn main() {
// Create a dummy file for the example to work
// In a real scenario, this file might not exist
std::fs::write("hello.txt", "CoddyKit User").unwrap();
match read_username_from_file() {
Ok(username) => println!("Username: {}", username),
Err(e) => println!("Error reading username: {}", e),
}
// Clean up the dummy file
std::fs::remove_file("hello.txt").unwrap();
}自定义错误类型
有时,内置错误类型不够具体。您可以使用枚举定义自己的自定义错误类型,以表示应用程序可能出现的不同失败情况。
这样可以让错误消息更清晰,也允许调用方以不同方式处理特定错误。
一种常见模式是创建一个枚举,列出函数或模块可能产生的所有错误。
让 `?` 处理自定义错误
如果希望 ? 运算符自动将较低层级的错误(例如 io::Error)转换为自定义错误类型,自定义错误枚举就必须为该较低层级的错误实现 From trait。
这会告诉 Rust 如何将一种错误类型转换为另一种错误类型,从而顺畅地传播错误。
use std::io;
use std::fs::File;
use std::io::Read;
#[derive(Debug)]
enum MyError {
Io(io::Error),
Parse(std::num::ParseIntError)
}
impl From<io::Error> for MyError {
fn from(error: io::Error) -> Self {
MyError::Io(error)
}
}
impl From<std::num::ParseIntError> for MyError {
fn from(error: std::num::ParseIntError) -> Self {
MyError::Parse(error)
}
}
fn read_config(path: &str) -> Result<i32, MyError> {
let mut file = File::open(path)?; // io::Error converted to MyError::Io
let mut contents = String::new();
file.read_to_string(&mut contents)?; // io::Error converted to MyError::Io
let value = contents.trim().parse::<i32>()?; // ParseIntError converted to MyError::Parse
Ok(value)
}
fn main() {
// Create a dummy file for the example
std::fs::write("config.txt", "123").unwrap();
match read_config("config.txt") {
Ok(val) => println!("Config value: {}", val),
Err(e) => println!("Failed to read config: {:?}", e),
}
std::fs::remove_file("config.txt").unwrap(); // Clean up
// Example with a non-existent file
match read_config("non_existent.txt") {
Ok(val) => println!("Config value: {}", val),
Err(e) => println!("Failed to read config: {:?}", e),
}
}检查您的理解
请考虑下面的 Rust 代码片段。如果文件 data.txt 不存在,输出会是什么?
回顾:健壮的错误处理
您已经掌握了 Rust 中健壮的错误处理!
Result枚举:区分成功时的Ok(T)与失败时的Err(E)。match:处理Result变体的基本方式。?运算符:将Err值沿调用栈向上传播的强大快捷方式。- 自定义错误:使用枚举定义自己的错误类型,以提高清晰度并实现针对性处理。
Fromtrait:使用?时,支持将较低层级的错误顺畅地转换为自定义错误类型。
接下来,探索高级类型系统特性!
常见问题解答
「使用 `Result` 进行健壮的错误处理」课时是免费的吗?
是的 — 「使用 `Result` 进行健壮的错误处理」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Learn Rust Coding 课程的其余内容,请升级到 CoddyKit PRO。 Learn Rust Coding 课程共包含 3 节课。
「使用 `Result` 进行健壮的错误处理」这节课中我会学到什么?
使用 `Result` 枚举、`?` 运算符和自定义错误类型实现符合 Rust 惯用方式的错误处理,构建更具韧性的应用。 你通过在浏览器中直接运行的动手代码来练习 Learn Rust Coding,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Learn Rust Coding 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Learn Rust Coding 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 3 节。
「使用 `Result` 进行健壮的错误处理」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Learn Rust Coding 课中编写并运行代码吗?
能。每节 Learn Rust Coding 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 使用模块组织代码
- 使用 crate 管理依赖
- 使用 `Result` 进行健壮的错误处理