目录

第十章 错误处理

10.1 Rust的错误处理哲学

Rust将错误分为两类:

对应机制:

10.2 不可恢复错误与panic!

什么是panic

当代码panic时,程序会:

1. 打印错误信息
2. 展开(unwind)调用栈,清理数据
3. 退出程序

触发panic

显式调用:

panic!("crash and burn");

隐式触发(bug):

let v = vec![1, 2, 3];
v[99];  // panic!索引越界

panic时的行为

默认情况下,panic会展开调用栈:

1. Rust沿着调用栈回溯
2. 清理每个函数中的数据
3. 打印错误信息并退出

设置panic为中止(abort):

在Cargo.toml中:

[profile.release]
panic = 'abort'

这样panic时不会清理,直接退出,可减小二进制文件大小。

使用panic!宏

fn main() {
    let guess: i32 = "not a number".parse().expect("需要一个数字!");
    // 或者
    // let guess: i32 = "not a number".parse().unwrap();
}

环境变量控制backtrace

设置环境变量查看完整的调用栈:

RUST_BACKTRACE=1 cargo run

使用full获取更详细的信息:

RUST_BACKTRACE=full cargo run

10.3 可恢复错误与Result

Result枚举

enum Result<T, E> {
    Ok(T),
    Err(E),
}

使用Result:

use std::fs::File;
 
let f = File::open("hello.txt");
 
let f = match f {
    Ok(file) => file,
    Err(error) => {
        println!("打开文件失败:{:?}", error);
        panic!("程序退出");
    }
};

匹配不同错误

use std::fs::File;
use std::io::ErrorKind;
 
let f = File::open("hello.txt");
 
let f = match f {
    Ok(file) => file,
    Err(error) => match error.kind() {
        ErrorKind::NotFound => match File::create("hello.txt") {
            Ok(fc) => fc,
            Err(e) => panic!("创建文件失败:{:?}", e),
        },
        other_error => {
            panic!("打开文件失败:{:?}", other_error)
        }
    },
};

10.4 快捷方法

unwrap

Ok时返回值,Err时panic:

let f = File::open("hello.txt").unwrap();

expect

unwrap的增强版,可自定义panic信息:

let f = File::open("hello.txt")
    .expect("hello.txt应该存在");

建议:在真实项目中优先使用expect,提供有用的错误信息。

unwrap_or和unwrap_or_else

提供默认值:

let s = "123";
let n = s.parse::<i32>().unwrap_or(0);  // 解析失败返回0
 
// 或使用闭包
let n = s.parse::<i32>().unwrap_or_else(|_| {
    println!("解析失败,使用默认值");
    0
});

10.5 传播错误

手动传播

use std::fs::File;
use std::io::{self, Read};
 
fn read_username_from_file() -> Result<String, io::Error> {
    let f = File::open("hello.txt");
 
    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };
 
    let mut s = String::new();
 
    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

?运算符

?是传播错误的简写:

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")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

?的行为:

?与match等价

let mut f = match File::open("hello.txt") {
    Ok(file) => file,
    Err(e) => return Err(e),
};
 
// 等价于
let mut f = File::open("hello.txt")?;

链式调用?

use std::fs;
use std::io;
 
fn read_username_from_file() -> Result<String, io::Error> {
    fs::read_to_string("hello.txt")
}

更进一步简化:

use std::io;
 
fn read_username_from_file() -> Result<String, io::Error> {
    std::fs::read_to_string("hello.txt")
}

10.6 ?与Option

?也可用于Option:

fn last_char_of_first_line(text: &str) -> Option<char> {
    text.lines().next()?.chars().last()
}
 
fn main() {
    assert_eq!(
        last_char_of_first_line("Hello, world\nHow are you"),
        Some('d')
    );
 
    assert_eq!(last_char_of_first_line(""), None);
    assert_eq!(last_char_of_first_line("\nhi"), None);
}

混合使用Result和Option:

use std::error::Error;
 
fn parse_and_double(s: Option<&str>) -> Result<i32, Box<dyn Error>> {
    let s = s.ok_or("没有输入")?;
    let n: i32 = s.parse()?;
    Ok(n * 2)
}

10.7 错误处理最佳实践

何时使用panic

应该panic的情况:

// 示例:unwrap在测试中可以接受
#[test]
fn test_something() {
    let result = some_operation().unwrap();
    assert_eq!(result, expected);
}
 
// 示例:不可能发生的情况
match some_enum {
    A => ...,
    B => ...,
    _ => unreachable!("枚举只有A和B两个变体"),
}

不应该panic的情况:

错误信息

提供有用的错误信息:

// 好的做法
let config = Config::build(&args)
    .expect("解析参数失败");
 
// 更好的做法:传播错误让用户处理
let config = Config::build(&args)?;

练习题

练习题10.1:安全除法

fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("除数不能为零"))
    } else {
        Ok(a / b)
    }
}
 
fn main() {
    match divide(10.0, 2.0) {
        Ok(result) => println!("结果:{}", result),
        Err(msg) => println!("错误:{}", msg),
    }
 
    match divide(10.0, 0.0) {
        Ok(result) => println!("结果:{}", result),
        Err(msg) => println!("错误:{}", msg),
    }
}

练习题10.2:读取配置文件

use std::fs;
use std::io;
 
fn read_config(filename: &str) -> Result<String, io::Error> {
    fs::read_to_string(filename)
}
 
fn main() {
    match read_config("config.txt") {
        Ok(content) => println!("配置内容:\n{}", content),
        Err(e) => println!("读取配置失败:{}", e),
    }
}

练习题10.3:级联错误处理

use std::fs::File;
use std::io::{self, BufRead, BufReader};
 
fn read_numbers(filename: &str) -> Result<Vec<i32>, Box<dyn std::error::Error>> {
    let file = File::open(filename)?;
    let reader = BufReader::new(file);
 
    let mut numbers = Vec::new();
 
    for line in reader.lines() {
        let line = line?;
        let num: i32 = line.parse()?;
        numbers.push(num);
    }
 
    Ok(numbers)
}
 
fn main() {
    match read_numbers("numbers.txt") {
        Ok(nums) => println!("数字:{:?}", nums),
        Err(e) => println!("错误:{}", e),
    }
}

本章小结

本章学习了Rust的错误处理机制:

Rust的错误处理强制程序员处理所有可能的错误情况,使程序更健壮。