跳到主要内容

Result 和 Option

Rust 的错误处理主要通过两个枚举类型:Result<T, E>Option<T>。这些类型让错误处理变得显式和安全。

Option 枚举

Option<T> 用于表示一个值可能存在也可能不存在的情况:

enum Option<T> {
Some(T),
None,
}

基本使用

fn find_word(text: &str, word: &str) -> Option<usize> {
text.find(word)
}

fn main() {
let text = "Hello, world!";

match find_word(text, "world") {
Some(index) => println!("找到 'world' 在位置: {}", index),
None => println!("没有找到 'world'"),
}

match find_word(text, "rust") {
Some(index) => println!("找到 'rust' 在位置: {}", index),
None => println!("没有找到 'rust'"),
}
}

Option 的常用方法

fn main() {
let some_number = Some(5);
let no_number: Option<i32> = None;

// is_some() 和 is_none()
println!("some_number 有值: {}", some_number.is_some());
println!("no_number 没有值: {}", no_number.is_none());

// unwrap() - 获取值,如果是 None 会 panic
println!("值: {}", some_number.unwrap());
// println!("{}", no_number.unwrap()); // 这会 panic!

// unwrap_or() - 提供默认值
println!("值或默认值: {}", no_number.unwrap_or(0));

// unwrap_or_else() - 使用闭包计算默认值
println!("值或计算的默认值: {}", no_number.unwrap_or_else(|| 42));
}

map 和 filter

fn main() {
let numbers = vec![Some(1), None, Some(3), Some(4), None];

// 使用 map 转换值
let doubled: Vec<Option<i32>> = numbers
.iter()
.map(|opt| opt.map(|x| x * 2))
.collect();

println!("翻倍后: {:?}", doubled);

// 过滤掉 None 值
let valid_numbers: Vec<i32> = numbers
.into_iter()
.filter_map(|opt| opt)
.collect();

println!("有效数字: {:?}", valid_numbers);
}

Result 枚举

Result<T, E> 用于表示操作可能成功或失败:

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

基本使用

fn divide(x: f64, y: f64) -> Result<f64, String> {
if y == 0.0 {
Err("不能除以零".to_string())
} else {
Ok(x / y)
}
}

fn main() {
match divide(10.0, 2.0) {
Ok(result) => println!("10 / 2 = {}", result),
Err(error) => println!("错误: {}", error),
}

match divide(10.0, 0.0) {
Ok(result) => println!("10 / 0 = {}", result),
Err(error) => println!("错误: {}", error),
}
}

Result 的常用方法

use std::fs::File;
use std::io::ErrorKind;

fn main() {
let file_result = File::open("hello.txt");

// is_ok() 和 is_err()
println!("文件打开成功: {}", file_result.is_ok());

// unwrap() - 获取成功值,失败时 panic
// let file = file_result.unwrap(); // 如果文件不存在会 panic

// unwrap_or_else() - 失败时执行闭包
let file = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("创建文件时出错: {:?}", error);
})
} else {
panic!("打开文件时出错: {:?}", error);
}
});

println!("文件处理完成");
}

expect 方法

expect 类似于 unwrap,但允许我们选择 panic! 的错误信息:

use std::fs::File;

fn main() {
let file = File::open("hello.txt")
.expect("无法打开 hello.txt 文件");

println!("文件打开成功");
}

传播错误

当编写一个其实现会调用一些可能失败的操作的函数时,除了在这个函数中处理错误外,还可以选择让调用者知道这个错误并决定如何处理:

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
let mut file = File::open("hello.txt")?;
let mut username = String::new();
file.read_to_string(&mut username)?;
Ok(username)
}

fn main() {
match read_username_from_file() {
Ok(username) => println!("用户名: {}", username),
Err(error) => println!("读取用户名失败: {}", error),
}
}

? 运算符

? 运算符是传播错误的简写:

use std::fs::File;
use std::io::{self, Read};

// 使用 ? 运算符的版本
fn read_username_from_file() -> Result<String, io::Error> {
let mut file = File::open("hello.txt")?;
let mut username = String::new();
file.read_to_string(&mut username)?;
Ok(username)
}

// 更简洁的版本
fn read_username_from_file_short() -> Result<String, io::Error> {
let mut username = String::new();
File::open("hello.txt")?.read_to_string(&mut username)?;
Ok(username)
}

// 最简洁的版本
fn read_username_from_file_shortest() -> Result<String, io::Error> {
std::fs::read_to_string("hello.txt")
}

fn main() {
match read_username_from_file() {
Ok(username) => println!("用户名: {}", username),
Err(error) => println!("错误: {}", error),
}
}

? 运算符与 Option

? 运算符也可以用于 Option 类型:

fn last_char_of_first_line(text: &str) -> Option<char> {
text.lines().next()?.chars().last()
}

fn main() {
let text = "Hello\nWorld";
match last_char_of_first_line(text) {
Some(ch) => println!("第一行的最后一个字符: {}", ch),
None => println!("没有找到字符"),
}

let empty_text = "";
match last_char_of_first_line(empty_text) {
Some(ch) => println!("第一行的最后一个字符: {}", ch),
None => println!("没有找到字符"),
}
}

组合 Option 和 Result

fn parse_and_double(s: &str) -> Result<Option<i32>, std::num::ParseIntError> {
match s.parse::<i32>() {
Ok(n) => Ok(Some(n * 2)),
Err(e) => Err(e),
}
}

// 使用 ? 运算符的版本
fn parse_and_double_short(s: &str) -> Result<Option<i32>, std::num::ParseIntError> {
Ok(Some(s.parse::<i32>()? * 2))
}

fn main() {
match parse_and_double("42") {
Ok(Some(n)) => println!("结果: {}", n),
Ok(None) => println!("没有值"),
Err(e) => println!("解析错误: {}", e),
}

match parse_and_double("abc") {
Ok(Some(n)) => println!("结果: {}", n),
Ok(None) => println!("没有值"),
Err(e) => println!("解析错误: {}", e),
}
}

自定义错误类型

use std::fmt;

#[derive(Debug)]
enum MathError {
DivisionByZero,
NegativeSquareRoot,
}

impl fmt::Display for MathError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MathError::DivisionByZero => write!(f, "不能除以零"),
MathError::NegativeSquareRoot => write!(f, "不能计算负数的平方根"),
}
}
}

impl std::error::Error for MathError {}

fn divide(x: f64, y: f64) -> Result<f64, MathError> {
if y == 0.0 {
Err(MathError::DivisionByZero)
} else {
Ok(x / y)
}
}

fn sqrt(x: f64) -> Result<f64, MathError> {
if x < 0.0 {
Err(MathError::NegativeSquareRoot)
} else {
Ok(x.sqrt())
}
}

fn main() {
match divide(10.0, 2.0) {
Ok(result) => println!("10 / 2 = {}", result),
Err(error) => println!("错误: {}", error),
}

match sqrt(-4.0) {
Ok(result) => println!("√(-4) = {}", result),
Err(error) => println!("错误: {}", error),
}
}

实际应用示例

配置文件解析

use std::fs;
use std::collections::HashMap;

#[derive(Debug)]
enum ConfigError {
FileNotFound,
ParseError(String),
MissingKey(String),
}

impl std::fmt::Display for ConfigError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ConfigError::FileNotFound => write!(f, "配置文件未找到"),
ConfigError::ParseError(msg) => write!(f, "解析错误: {}", msg),
ConfigError::MissingKey(key) => write!(f, "缺少配置项: {}", key),
}
}
}

impl std::error::Error for ConfigError {}

struct Config {
settings: HashMap<String, String>,
}

impl Config {
fn load(filename: &str) -> Result<Config, ConfigError> {
let content = fs::read_to_string(filename)
.map_err(|_| ConfigError::FileNotFound)?;

let mut settings = HashMap::new();

for line in content.lines() {
if line.trim().is_empty() || line.starts_with('#') {
continue;
}

let parts: Vec<&str> = line.split('=').collect();
if parts.len() != 2 {
return Err(ConfigError::ParseError(
format!("无效的行格式: {}", line)
));
}

settings.insert(
parts[0].trim().to_string(),
parts[1].trim().to_string(),
);
}

Ok(Config { settings })
}

fn get(&self, key: &str) -> Result<&String, ConfigError> {
self.settings.get(key)
.ok_or_else(|| ConfigError::MissingKey(key.to_string()))
}

fn get_or_default(&self, key: &str, default: &str) -> String {
self.settings.get(key)
.map(|s| s.clone())
.unwrap_or_else(|| default.to_string())
}
}

fn main() {
match Config::load("config.txt") {
Ok(config) => {
match config.get("database_url") {
Ok(url) => println!("数据库 URL: {}", url),
Err(error) => println!("错误: {}", error),
}

let port = config.get_or_default("port", "8080");
println!("端口: {}", port);
}
Err(error) => println!("加载配置失败: {}", error),
}
}

链式操作

fn process_data(input: &str) -> Result<i32, Box<dyn std::error::Error>> {
let trimmed = input.trim();
let parsed = trimmed.parse::<i32>()?;
let doubled = parsed * 2;

if doubled > 100 {
Err("结果太大".into())
} else {
Ok(doubled)
}
}

fn main() {
let inputs = vec!["42", " 25 ", "abc", "60"];

for input in inputs {
match process_data(input) {
Ok(result) => println!("'{}' -> {}", input, result),
Err(error) => println!("'{}' -> 错误: {}", input, error),
}
}
}

最佳实践

  1. 优先使用 Result 和 Option:而不是 panic! 或返回特殊值
  2. 使用 ? 运算符:简化错误传播
  3. 提供有意义的错误信息:使用 expect 而不是 unwrap
  4. 创建自定义错误类型:为复杂应用提供更好的错误处理
  5. 合理使用 unwrap:只在确定不会失败的情况下使用
// 好的错误处理示例
fn safe_divide(x: f64, y: f64) -> Option<f64> {
if y == 0.0 {
None
} else {
Some(x / y)
}
}

fn parse_positive_number(s: &str) -> Result<u32, String> {
let num = s.parse::<u32>()
.map_err(|_| format!("'{}' 不是有效的数字", s))?;

if num == 0 {
Err("数字必须大于零".to_string())
} else {
Ok(num)
}
}

fn main() {
// 使用 Option
match safe_divide(10.0, 2.0) {
Some(result) => println!("结果: {}", result),
None => println!("不能除以零"),
}

// 使用 Result
match parse_positive_number("42") {
Ok(num) => println!("解析成功: {}", num),
Err(error) => println!("错误: {}", error),
}
}