5. 错误处理篇¶
对应章节:第9章(错误处理)。
本篇掌握 Rust 的错误处理体系:Result、自定义错误、?运算符与 JSON 持久化。
项目 12:银行账户管理器¶
目标:自定义错误类型 + JSON 持久化,掌握 Rust 的错误处理体系。
创建项目¶
功能要求¶
- 支持开立账户、存款、取款、查余额
- 余额不足、金额非法等返回自定义错误
- 数据用 JSON 保存到文件
知识点¶
| 知识点 | 说明 |
|---|---|
自定义 enum 错误 |
定义错误种类 |
Display + Error trait |
让错误可打印、可传播 |
? 运算符 |
错误自动传播 |
map_err / ok_or_else |
转换错误类型 |
serde |
序列化/反序列化 |
entry().or_insert(...) |
HashMap 取或建 |
代码¶
依赖已由 cargo add 添加(cargo search serde 可查最新版本),无需手动编辑 Cargo.toml。
src/main.rs:
use std::collections::HashMap;
use std::fmt;
use std::fs;
// ============ 自定义错误类型 ============
// 用枚举表示"可能出现的错误种类"
#[derive(Debug)]
enum BankError {
// 余额不足:携带余额与取款额两个字段,便于给用户清晰提示
InsufficientFunds { balance: f64, amount: f64 },
InvalidAmount(f64), // 金额非法
AccountNotFound(String), // 账户不存在
Io(std::io::Error), // IO 错误
}
// 实现 Display:让错误可以用 {} 打印成可读文本
impl fmt::Display for BankError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
BankError::InsufficientFunds { balance, amount } => {
write!(f, "余额不足:余额 {},取款 {}", balance, amount)
}
BankError::InvalidAmount(amount) => {
write!(f, "金额不合法:{}", amount)
}
BankError::AccountNotFound(name) => {
write!(f, "账户不存在:{}", name)
}
BankError::Io(err) => write!(f, "IO 错误:{}", err),
}
}
}
// 实现标准 Error trait:让 ? 能自动传播、并可与别的错误互转
impl std::error::Error for BankError {}
// ============ 数据模型 ============
// derive 两个宏:自动生成"序列化成 JSON"和"从 JSON 反序列化"
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct Account {
name: String,
balance: f64,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct Bank {
accounts: HashMap<String, Account>,
}
impl Bank {
fn new() -> Bank {
Bank { accounts: HashMap::new() }
}
// 存款:返回 Result<f64, BankError>,余额在 Ok 里
fn deposit(&mut self, name: &str, amount: f64) -> Result<f64, BankError> {
if amount <= 0.0 {
return Err(BankError::InvalidAmount(amount)); // 显式返回错误
}
// entry().or_insert():key 不存在则插入初始账户,返回 &mut Account
// 一步完成"取或建",避免先查再插的两步麻烦
let acc = self.accounts.entry(name.to_string()).or_insert(Account {
name: name.to_string(),
balance: 0.0,
});
acc.balance += amount;
Ok(acc.balance)
}
// 取款
fn withdraw(&mut self, name: &str, amount: f64) -> Result<f64, BankError> {
if amount <= 0.0 {
return Err(BankError::InvalidAmount(amount));
}
// get_mut 返回 Option<&mut Account>
// ok_or_else:None 时用闭包构造错误并转成 Result
// 后面接 ?:Err 时直接返回
let acc = self.accounts
.get_mut(name)
.ok_or_else(|| BankError::AccountNotFound(name.to_string()))?;
if acc.balance < amount {
return Err(BankError::InsufficientFunds {
balance: acc.balance,
amount,
});
}
acc.balance -= amount;
Ok(acc.balance)
}
fn balance(&self, name: &str) -> Result<f64, BankError> {
let acc = self.accounts
.get(name)
.ok_or_else(|| BankError::AccountNotFound(name.to_string()))?;
Ok(acc.balance)
}
// ============ JSON 持久化 ============
fn save(&self, path: &str) -> Result<(), BankError> {
// serde_json::to_string_pretty:生成带缩进的 JSON,方便阅读
// map_err:把 serde_json::Error 转成我们的 BankError,让 ? 统一传播
let json = serde_json::to_string_pretty(self)
.map_err(|e| BankError::Io(std::io::Error::other(e)))?;
fs::write(path, json).map_err(BankError::Io) // IO 错误也转成 BankError
}
fn load(path: &str) -> Bank {
// 链式 Option:读取成功 && 解析成功才返回 Bank
// unwrap_or_else:任一失败则回退到空银行(不 panic)
fs::read_to_string(path)
.ok()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_else(Bank::new)
}
}
fn main() {
let mut bank = Bank::load("bank.json");
// 存款:match 处理 Result
match bank.deposit("小明", 100.0) {
Ok(b) => println!("存款成功,余额 {}", b),
Err(e) => println!("存款失败:{}", e), // Display 让 e 可打印
}
// 取款超余额 → 触发自定义错误 InsufficientFunds
match bank.withdraw("小明", 200.0) {
Ok(b) => println!("取款成功,余额 {}", b),
Err(e) => println!("取款失败:{}", e),
}
// 查不存在的账户 → AccountNotFound
match bank.balance("小红") {
Ok(b) => println!("余额 {}", b),
Err(e) => println!("查询失败:{}", e),
}
// 保存到文件(bank.json 会出现在项目目录)
if let Err(e) = bank.save("bank.json") {
println!("保存失败:{}", e);
}
}
运行¶
第二次运行会读取 bank.json(加载上次的账户),可以观察数据持久化。
注意事项¶
- 自定义错误三件套:
#[derive(Debug)]+impl Display+impl std::error::Error。?才能把自定义错误向上传播。 entry().or_insert(...):账户不存在则自动插入初始账户,一步完成"取或建"——第13章的entry().or_default()是它的更简写版。ok_or_else(|| ...):把Option转成Result,None时用闭包构造错误——比unwrap_or更专业。map_err转换错误:IO 错误、JSON 错误都要转成BankError::Io(...),?才能统一传播。unwrap_or_else(Bank::new):读取或解析失败时回退到空银行,而不是 panic。