“文字地牢”小游戏通关 Rust 入门-错误处理与输入校验
·
错误处理与输入校验
上文我们大致了解了"文字地牢"小游戏的地图生成与迭代器,那么我们本章的目标是理解 Result/Option、?、match 与早返回;区分 panic! 与可恢复错误;介绍 thiserror/anyhow。
基本概念
Result 和 Option 类型
Rust使用 Result和 Option类型来进行错误处理,这是一种类型安全的错误处理方式,强制开发者在编译时考虑错误情况。
Result<T, E>类型表示可能成功返回 T值或失败返回 E错误的计算:
enum Result<T, E> {
Ok(T),
Err(E),
}
Option<T>类型表示可能有值 T或无值的情况:
enum Option<T> {
Some(T),
None,
}
可恢复错误 vs 不可恢复错误
Rust区分可恢复错误(如文件不存在)和不可恢复错误(如数组越界),并提供不同的处理机制。
可恢复错误:
- 使用
Result<T, E>类型表示 - 可以被调用者处理或传播
- 适用于预期可能发生的错误情况
不可恢复错误:
- 使用
panic!宏处理 - 程序会立即终止(除非被捕获)
- 适用于程序bug或不可恢复的状态
错误传播
通过 ?操作符,可以简洁地将错误传播给调用者,而无需在每个函数中都显式处理错误。
?操作符的工作原理:
- 如果值是
Ok(T),则提取T值继续执行 - 如果值是
Err(E),则立即从当前函数返回错误
错误类型转换
Rust通过 From trait实现错误类型之间的自动转换,简化错误处理代码。
如何使用
Result 和 Option 的基本使用
// 使用 Result
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("Division by zero".to_string())
} else {
Ok(a / b)
}
}
// 使用 Option
fn find_element(vec: &[i32], target: i32) -> Option<usize> {
vec.iter().position(|&x| x == target)
}
// 处理 Result
match divide(10.0, 2.0) {
Ok(result) => println!("Result: {}", result),
Err(error) => println!("Error: {}", error),
}
// 处理 Option
match find_element(&[1, 2, 3], 2) {
Some(index) => println!("Found at index: {}", index),
None => println!("Not found"),
}
错误传播和 ? 操作符
// 传统错误处理
fn read_file_traditional(path: &str) -> Result<String, std::io::Error> {
let file = match std::fs::File::open(path) {
Ok(file) => file,
Err(error) => return Err(error),
};
let mut contents = String::new();
match file.read_to_string(&mut contents) {
Ok(_) => Ok(contents),
Err(error) => Err(error),
}
}
// 使用 ? 操作符简化
fn read_file_simplified(path: &str) -> Result<String, std::io::Error> {
let mut file = std::fs::File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
自定义错误类型
#[derive(Debug)]
enum MyError {
Io(std::io::Error),
Parse(std::num::ParseIntError),
Custom(String),
}
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
MyError::Io(err) => write!(f, "IO error: {}", err),
MyError::Parse(err) => write!(f, "Parse error: {}", err),
MyError::Custom(msg) => write!(f, "Custom error: {}", msg),
}
}
}
impl std::error::Error for MyError {}
// 实现 From trait 进行自动转换
impl From<std::io::Error> for MyError {
fn from(error: std::io::Error) -> Self {
MyError::Io(error)
}
}
可恢复 vs 不可恢复
- 可恢复:文件不存在、格式错误,用
Result表达并返回给调用者。 - 不可恢复:逻辑断言失败,用
panic!(测试中可结合#[should_panic])。
错误边界与转换
- 在公共边界(如
io::load_state)返回语义清晰的错误类型(AppError)。 - 使用
From/?简化底层错误的转换。
工具
anyhow:快速聚合错误,适合应用层。thiserror:优雅定义错误枚举,自动实现Display。
注意事项
- 始终处理
Result和Option类型,不要忽略它们。 - 合理区分可恢复错误和不可恢复错误,避免滥用
panic!。 - 在公共API边界返回语义清晰的错误类型。
- 使用
?操作符简化错误传播,但要注意函数返回类型必须是Result。 - 为错误添加上下文信息,便于调试和用户理解。
- 避免在库代码中使用
panic!,除非是不可恢复的错误。 - 使用
unwrap()和expect()时要谨慎,只在确定不会失败的情况下使用。 - 考虑使用
anyhow或thiserrorcrate来简化错误处理。
本项目中的使用
在我们的项目中,我们定义了一个自定义的错误类型 AppError,并为它实现了 From trait来简化错误转换:
pub type Result<T> = std::result::Result<T, AppError>;
#[derive(Debug)]
pub enum AppError {
Io(std::io::Error),
Serde(serde_json::Error),
}
impl std::fmt::Display for AppError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AppError::Io(e) => write!(f, "IO error: {}", e),
AppError::Serde(e) => write!(f, "Serde error: {}", e),
}
}
}
impl std::error::Error for AppError {}
impl From<std::io::Error> for AppError {
fn from(e: std::io::Error) -> Self { AppError::Io(e) }
}
impl From<serde_json::Error> for AppError {
fn from(e: serde_json::Error) -> Self { AppError::Serde(e) }
}
在IO操作中,我们使用 ?操作符来传播错误:
pub fn load_state(path: &str) -> Result<GameState> {
let s = std::fs::read_to_string(path)?;
let state: GameState = serde_json::from_str(&s)?;
Ok(state)
}
错误处理的最佳实践
在地牢探险游戏中,我们可以应用以下错误处理模式:
- 输入验证:
fn parse_command(input: &str) -> Result<Command, AppError> {
match input.trim().to_lowercase().as_str() {
"w" => Ok(Command::Move(Direction::North)),
"a" => Ok(Command::Move(Direction::West)),
"s" => Ok(Command::Move(Direction::South)),
"d" => Ok(Command::Move(Direction::East)),
"save" => Ok(Command::Save),
"load" => Ok(Command::Load),
"q" => Ok(Command::Quit),
_ => Err(AppError::Custom("Unknown command".to_string())),
}
}
- 文件操作错误处理:
pub fn save_game(path: &str, state: &GameState) -> Result<()> {
let json = serde_json::to_string_pretty(state)
.map_err(|e| AppError::Serialization(format!("Failed to serialize: {}", e)))?;
std::fs::write(path, json)
.map_err(|e| AppError::Io(format!("Failed to write file: {}", e)))?;
Ok(())
}
- 游戏逻辑错误:
impl Game {
fn move_player(&mut self, direction: Direction) -> Result<()> {
let new_position = self.calculate_new_position(direction);
if !self.is_valid_position(new_position) {
return Err(AppError::InvalidMove("Cannot move there".to_string()));
}
self.state.player.position = new_position;
Ok(())
}
}
高级错误处理技术
使用 anyhow 简化错误处理
use anyhow::{Result, Context, bail};
fn process_file(path: &str) -> Result<String> {
let contents = std::fs::read_to_string(path)
.with_context(|| format!("Failed to read file: {}", path))?;
if contents.is_empty() {
bail!("File is empty: {}", path);
}
Ok(contents)
}
使用 thiserror 定义错误类型
use thiserror::Error;
#[derive(Error, Debug)]
pub enum GameError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("Invalid move: {0}")]
InvalidMove(String),
#[error("Game over: {0}")]
GameOver(String),
}
错误处理策略
库 vs 应用程序
- 库代码:返回具体的错误类型,允许调用者决定如何处理
- 应用程序:可以使用
anyhow等通用错误类型简化处理
错误日志记录
use log::{error, warn, info};
fn handle_error(error: &AppError) {
match error {
AppError::Io(e) => error!("IO error occurred: {}", e),
AppError::Serde(e) => warn!("Serialization error: {}", e),
_ => info!("Other error: {:?}", error),
}
}
练习:
- 为
load_state区分"文件不存在"与"格式错误"并提示不同信息。 - 使用
thiserror重写AppError(可选)。
概念补充
- 错误类型建模:优先用枚举表达可区分的错误分支,利于上层处理与测试断言。
OptionvsResult:缺失但非错误用Option;需要错误信息与分支处理用Result。?传播与上下文:结合anyhow::Context/eyre::WrapErr为错误增加语境(“读取 save.json 失败”)。- 日志与错误:错误路径上写日志但不要静默吞错;返回错误让上层决定行为(重试/提示/退出)。
- 边界策略:库层返回富信息错误类型;应用层可聚合为
anyhow::Error简化主流程。 - 恢复与重试:对可恢复错误(I/O、网络)考虑退避重试;对不可恢复错误尽早
panic!/assert!暴露。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)