在昨天學習了列舉和模式匹配之後,今天我們要來看看 Rust 最重要的特性之一 - 錯誤處理。Rust 通過型別系統強制你考慮和處理可能出現的錯誤讓程式更加穩定和可靠。不像其他語言可能會拋出異常或返回特殊值,Rust 使用 Result
和 Option
型別來明確表示操作可能失敗。
在其他語言中,錯誤處理通常是這樣的:
// 類似 Java/C# 的方式(Rust 不支援)
// try {
// let file = open_file("data.txt");
// let content = file.read();
// } catch (IOException e) {
// println!("錯誤: {}", e);
// }
// 或類似 C 的方式(容易忽略錯誤)
// FILE* file = fopen("data.txt", "r");
// if (file == NULL) { // 容易忘記檢查!
// // 錯誤處理
// }
Rust 的方式:
use std::fs;
fn read_file_content() -> Result<String, std::io::Error> {
fs::read_to_string("data.txt")
}
fn main() {
match read_file_content() {
Ok(content) => println!("檔案內容: {}", content),
Err(error) => println!("讀取失敗: {}", error),
}
}
關鍵差異:
Result
,否則編譯器會警告enum Result<T, E> {
Ok(T), // 成功,包含結果值
Err(E), // 失敗,包含錯誤值
}
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("除零錯誤"))
} else {
Ok(a / b)
}
}
fn main() {
// 方式 1:使用 match
match divide(10.0, 2.0) {
Ok(result) => println!("結果: {}", result),
Err(error) => println!("錯誤: {}", error),
}
// 方式 2:使用 if let
if let Ok(result) = divide(10.0, 3.0) {
println!("成功計算: {}", result);
}
// 方式 3:使用方法
let result = divide(15.0, 3.0)
.unwrap_or(0.0); // 錯誤時使用預設值
println!("結果或預設值: {}", result);
}
fn demo_unwrap() {
let good_result = divide(10.0, 2.0);
let value = good_result.unwrap(); // 確定不會出錯時使用
println!("值: {}", value);
// 提供更好的錯誤訊息
let value = divide(10.0, 2.0)
.expect("這個計算不應該失敗");
println!("值: {}", value);
// 危險!如果結果是 Err,程式會 panic
// let bad_value = divide(10.0, 0.0).unwrap(); // 會 panic
}
fn demo_unwrap_or() {
// 提供預設值
let result1 = divide(10.0, 0.0)
.unwrap_or(-1.0);
println!("結果或預設值: {}", result1);
// 使用閉包提供預設值
let result2 = divide(10.0, 0.0)
.unwrap_or_else(|err| {
println!("計算失敗: {}", err);
0.0 // 回傳預設值
});
println!("結果或計算的預設值: {}", result2);
}
fn demo_map() {
// 對成功結果進行轉換
let result = divide(10.0, 2.0)
.map(|x| x * 2.0) // 如果成功,結果乘以 2
.map(|x| format!("答案是: {}", x)); // 轉換成字串
match result {
Ok(msg) => println!("{}", msg),
Err(e) => println!("錯誤: {}", e),
}
// 對錯誤進行轉換
let result = divide(10.0, 0.0)
.map_err(|e| format!("數學錯誤: {}", e)); // 轉換錯誤型別
match result {
Ok(value) => println!("值: {}", value),
Err(e) => println!("{}", e),
}
}
問號運算子是 Rust 錯誤處理的核心特性,它讓錯誤傳播變得簡潔:
use std::fs;
use std::io;
// 不使用 ? 運算子(繁瑣)
fn read_username_from_file_verbose() -> Result<String, io::Error> {
let username_file_result = fs::read_to_string("username.txt");
let username = match username_file_result {
Ok(content) => content,
Err(e) => return Err(e),
};
Ok(username.trim().to_string())
}
// 使用 ? 運算子(簡潔)
fn read_username_from_file() -> Result<String, io::Error> {
let username = fs::read_to_string("username.txt")?;
Ok(username.trim().to_string())
}
// 更簡潔的版本
fn read_username_simple() -> Result<String, io::Error> {
Ok(fs::read_to_string("username.txt")?.trim().to_string())
}
// ? 運算子等同於這個 match
fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
// 使用 ?
let num = s.parse::<i32>()?;
Ok(num * 2)
}
// 等同於:
fn parse_number_expanded(s: &str) -> Result<i32, std::num::ParseIntError> {
let num = match s.parse::<i32>() {
Ok(n) => n,
Err(e) => return Err(e),
};
Ok(num * 2)
}
#[derive(Debug)]
enum MathError {
DivisionByZero,
NegativeSquareRoot,
Overflow,
}
impl std::fmt::Display for MathError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
MathError::DivisionByZero => write!(f, "除零錯誤"),
MathError::NegativeSquareRoot => write!(f, "負數不能開平方根"),
MathError::Overflow => write!(f, "數值溢位"),
}
}
}
impl std::error::Error for MathError {}
fn safe_divide(a: f64, b: f64) -> Result<f64, MathError> {
if b == 0.0 {
Err(MathError::DivisionByZero)
} else {
Ok(a / b)
}
}
fn safe_sqrt(x: f64) -> Result<f64, MathError> {
if x < 0.0 {
Err(MathError::NegativeSquareRoot)
} else {
Ok(x.sqrt())
}
}
fn complex_calculation(a: f64, b: f64, c: f64) -> Result<f64, MathError> {
let quotient = safe_divide(a, b)?;
let sum = quotient + c;
let result = safe_sqrt(sum)?;
Ok(result)
}
fn main() {
match complex_calculation(16.0, 2.0, 9.0) {
Ok(result) => println!("計算結果: {}", result),
Err(e) => println!("計算失敗: {}", e),
}
// 測試不同的錯誤情況
match complex_calculation(10.0, 0.0, 5.0) {
Ok(result) => println!("結果: {}", result),
Err(MathError::DivisionByZero) => println!("出現除零錯誤!"),
Err(e) => println!("其他錯誤: {}", e),
}
}
讓我們為任務管理系統添加完整的錯誤處理:
use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone)]
pub enum TaskError {
NotFound(u32),
AlreadyExists(String),
InvalidStatus {
current: String,
requested: String
},
InvalidPriority(i32),
EmptyTitle,
DatabaseError(String),
}
impl fmt::Display for TaskError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
TaskError::NotFound(id) => write!(f, "找不到 ID {} 的任務", id),
TaskError::AlreadyExists(title) => write!(f, "任務 '{}' 已經存在", title),
TaskError::InvalidStatus { current, requested } => {
write!(f, "無法從 '{}' 狀態變更為 '{}'", current, requested)
},
TaskError::InvalidPriority(p) => write!(f, "無效的優先順序: {}", p),
TaskError::EmptyTitle => write!(f, "任務標題不能為空"),
TaskError::DatabaseError(msg) => write!(f, "資料庫錯誤: {}", msg),
}
}
}
impl std::error::Error for TaskError {}
#[derive(Debug, Clone, PartialEq)]
pub enum TaskStatus {
Todo,
InProgress,
Done,
Cancelled,
}
impl fmt::Display for TaskStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
TaskStatus::Todo => write!(f, "待辦"),
TaskStatus::InProgress => write!(f, "進行中"),
TaskStatus::Done => write!(f, "已完成"),
TaskStatus::Cancelled => write!(f, "已取消"),
}
}
}
#[derive(Debug, Clone)]
pub struct Task {
pub id: u32,
pub title: String,
pub description: String,
pub status: TaskStatus,
pub priority: u32,
}
impl Task {
pub fn new(id: u32, title: String, description: String) -> Result<Self, TaskError> {
if title.trim().is_empty() {
return Err(TaskError::EmptyTitle);
}
Ok(Task {
id,
title: title.trim().to_string(),
description,
status: TaskStatus::Todo,
priority: 1,
})
}
pub fn set_priority(&mut self, priority: u32) -> Result<(), TaskError> {
if priority == 0 || priority > 5 {
Err(TaskError::InvalidPriority(priority as i32))
} else {
self.priority = priority;
Ok(())
}
}
pub fn change_status(&mut self, new_status: TaskStatus) -> Result<(), TaskError> {
let valid_transition = match (&self.status, &new_status) {
(TaskStatus::Todo, TaskStatus::InProgress) => true,
(TaskStatus::Todo, TaskStatus::Cancelled) => true,
(TaskStatus::InProgress, TaskStatus::Done) => true,
(TaskStatus::InProgress, TaskStatus::Todo) => true,
(TaskStatus::InProgress, TaskStatus::Cancelled) => true,
(current, new) if current == new => true,
_ => false,
};
if valid_transition {
self.status = new_status;
Ok(())
} else {
Err(TaskError::InvalidStatus {
current: self.status.to_string(),
requested: new_status.to_string(),
})
}
}
}
pub struct TaskManager {
tasks: HashMap<u32, Task>,
next_id: u32,
titles: std::collections::HashSet<String>,
}
impl TaskManager {
pub fn new() -> Self {
TaskManager {
tasks: HashMap::new(),
next_id: 1,
titles: std::collections::HashSet::new(),
}
}
pub fn create_task(&mut self, title: String, description: String) -> Result<u32, TaskError> {
// 檢查標題是否已存在
if self.titles.contains(&title) {
return Err(TaskError::AlreadyExists(title));
}
let task = Task::new(self.next_id, title.clone(), description)?;
let id = task.id;
self.tasks.insert(id, task);
self.titles.insert(title);
self.next_id += 1;
Ok(id)
}
pub fn get_task(&self, id: u32) -> Result<&Task, TaskError> {
self.tasks.get(&id).ok_or(TaskError::NotFound(id))
}
pub fn get_task_mut(&mut self, id: u32) -> Result<&mut Task, TaskError> {
self.tasks.get_mut(&id).ok_or(TaskError::NotFound(id))
}
pub fn update_task_status(&mut self, id: u32, status: TaskStatus) -> Result<(), TaskError> {
let task = self.get_task_mut(id)?;
task.change_status(status)?;
Ok(())
}
pub fn set_task_priority(&mut self, id: u32, priority: u32) -> Result<(), TaskError> {
let task = self.get_task_mut(id)?;
task.set_priority(priority)?;
Ok(())
}
pub fn delete_task(&mut self, id: u32) -> Result<Task, TaskError> {
let task = self.tasks.remove(&id).ok_or(TaskError::NotFound(id))?;
self.titles.remove(&task.title);
Ok(task)
}
pub fn list_tasks(&self) -> Vec<&Task> {
self.tasks.values().collect()
}
pub fn list_tasks_by_status(&self, status: TaskStatus) -> Vec<&Task> {
self.tasks.values()
.filter(|task| task.status == status)
.collect()
}
// 批次操作,展示錯誤收集
pub fn complete_multiple_tasks(&mut self, ids: Vec<u32>) -> Result<Vec<u32>, Vec<(u32, TaskError)>> {
let mut completed = Vec::new();
let mut errors = Vec::new();
for id in ids {
match self.update_task_status(id, TaskStatus::Done) {
Ok(()) => completed.push(id),
Err(e) => errors.push((id, e)),
}
}
if errors.is_empty() {
Ok(completed)
} else {
Err(errors)
}
}
// 示範複合操作的錯誤處理
pub fn create_and_start_task(&mut self, title: String, description: String, priority: u32) -> Result<u32, TaskError> {
let id = self.create_task(title, description)?;
self.set_task_priority(id, priority)?;
self.update_task_status(id, TaskStatus::InProgress)?;
Ok(id)
}
}
// 輔助函式:安全地解析優先順序
pub fn parse_priority(input: &str) -> Result<u32, TaskError> {
input.parse()
.map_err(|_| TaskError::InvalidPriority(-1))
.and_then(|p| {
if p >= 1 && p <= 5 {
Ok(p)
} else {
Err(TaskError::InvalidPriority(p as i32))
}
})
}
// 輔助函式:安全地解析狀態
pub fn parse_status(input: &str) -> Result<TaskStatus, TaskError> {
match input.to_lowercase().as_str() {
"todo" | "待辦" => Ok(TaskStatus::Todo),
"progress" | "進行中" => Ok(TaskStatus::InProgress),
"done" | "完成" => Ok(TaskStatus::Done),
"cancelled" | "取消" => Ok(TaskStatus::Cancelled),
_ => Err(TaskError::InvalidStatus {
current: "unknown".to_string(),
requested: input.to_string(),
}),
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut manager = TaskManager::new();
// 建立任務,展示成功路徑
println!("=== 建立任務 ===");
match manager.create_task(
String::from("學習 Rust 錯誤處理"),
String::from("深入理解 Result 和錯誤處理模式")
) {
Ok(id) => println!("✓ 成功建立任務 ID: {}", id),
Err(e) => println!("✗ 建立任務失敗: {}", e),
}
// 嘗試建立重複標題的任務
match manager.create_task(
String::from("學習 Rust 錯誤處理"), // 重複標題
String::from("重複的任務")
) {
Ok(id) => println!("✓ 成功建立任務 ID: {}", id),
Err(e) => println!("✗ 預期的錯誤: {}", e),
}
// 建立更多任務用於測試
let task_ids = vec![
manager.create_task(String::from("實作專案"), String::from("建立完整專案"))?,
manager.create_task(String::from("寫測試"), String::from("編寫單元測試"))?,
manager.create_task(String::from("文檔撰寫"), String::from("撰寫使用者文檔"))?,
];
println!("\n=== 設定任務屬性 ===");
// 設定優先順序
for (i, &id) in task_ids.iter().enumerate() {
let priority = (i % 5) + 1; // 1-5
match manager.set_task_priority(id, priority as u32) {
Ok(()) => println!("✓ 任務 {} 優先順序設為 {}", id, priority),
Err(e) => println!("✗ 設定失敗: {}", e),
}
}
// 測試無效優先順序
match manager.set_task_priority(task_ids[0], 10) { // 無效值
Ok(()) => println!("✓ 設定成功"),
Err(e) => println!("✗ 預期的錯誤: {}", e),
}
println!("\n=== 狀態變更 ===");
// 開始第一個任務
match manager.update_task_status(task_ids[0], TaskStatus::InProgress) {
Ok(()) => println!("✓ 任務 {} 已開始", task_ids[0]),
Err(e) => println!("✗ 狀態變更失敗: {}", e),
}
// 嘗試不合法的狀態變更
match manager.update_task_status(task_ids[1], TaskStatus::Done) { // 從待辦直接到完成
Ok(()) => println!("✓ 狀態變更成功"),
Err(e) => println!("✗ 預期的錯誤: {}", e),
}
println!("\n=== 複合操作 ===");
// 使用複合操作
match manager.create_and_start_task(
String::from("緊急任務"),
String::from("需要立即處理的任務"),
5
) {
Ok(id) => println!("✓ 緊急任務建立並開始,ID: {}", id),
Err(e) => println!("✗ 複合操作失敗: {}", e),
}
println!("\n=== 批次操作 ===");
// 批次完成任務(先將它們設為進行中)
for &id in &task_ids[1..] {
let _ = manager.update_task_status(id, TaskStatus::InProgress);
}
match manager.complete_multiple_tasks(task_ids.clone()) {
Ok(completed) => println!("✓ 完成了 {} 個任務: {:?}", completed.len(), completed),
Err(errors) => {
println!("✗ 批次操作部分失敗:");
for (id, error) in errors {
println!(" 任務 {}: {}", id, error);
}
}
}
println!("\n=== 任務列表 ===");
// 列出所有任務
let tasks = manager.list_tasks();
for task in tasks {
println!("任務 {}: {} [{}] (優先順序: {})",
task.id, task.title, task.status, task.priority);
}
println!("\n=== 輔助函式測試 ===");
// 測試輔助函式
match parse_priority("3") {
Ok(p) => println!("✓ 解析優先順序: {}", p),
Err(e) => println!("✗ 解析失敗: {}", e),
}
match parse_priority("invalid") {
Ok(p) => println!("✓ 解析優先順序: {}", p),
Err(e) => println!("✗ 預期的錯誤: {}", e),
}
match parse_status("progress") {
Ok(status) => println!("✓ 解析狀態: {}", status),
Err(e) => println!("✗ 解析失敗: {}", e),
}
Ok(())
}
// 快速失敗:使用 unwrap/expect(確定不會失敗時)
let config_value = std::env::var("CONFIG_PATH")
.expect("CONFIG_PATH 環境變數必須設定");
// 提供預設值:使用 unwrap_or
let timeout = std::env::var("TIMEOUT")
.unwrap_or_else(|_| "30".to_string())
.parse()
.unwrap_or(30);
// 傳播錯誤:使用 ?
fn load_config() -> Result<Config, ConfigError> {
let path = std::env::var("CONFIG_PATH")?;
let content = std::fs::read_to_string(path)?;
let config: Config = serde_json::from_str(&content)?;
Ok(config)
}
// 好的錯誤型別設計
#[derive(Debug)]
pub enum ApplicationError {
Io(std::io::Error),
Parse(serde_json::Error),
Validation(String),
Network {
url: String,
status_code: u16
},
}
// 實作 From 特徵以支援 ? 運算子
impl From<std::io::Error> for ApplicationError {
fn from(error: std::io::Error) -> Self {
ApplicationError::Io(error)
}
}
impl From<serde_json::Error> for ApplicationError {
fn from(error: serde_json::Error) -> Self {
ApplicationError::Parse(error)
}
}
# Cargo.toml
[dependencies]
anyhow = "1.0"
thiserror = "1.0"
use anyhow::{Context, Result};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DataStoreError {
#[error("資料驗證失敗")]
Validation(#[from] ValidationError),
#[error("網路錯誤")]
Network(#[from] reqwest::Error),
#[error("找不到 ID 為 {id} 的項目")]
NotFound { id: u64 },
#[error("資料庫連線失敗")]
Database(#[from] sqlx::Error),
}
fn process_data(id: u64) -> Result<String> {
let data = fetch_data(id)
.with_context(|| format!("無法取得 ID {} 的資料", id))?;
let processed = validate_and_process(&data)
.context("資料處理失敗")?;
Ok(processed)
}
#[derive(Debug)]
enum CalculatorError {
DivisionByZero,
InvalidOperation,
Overflow,
}
// 實作以下函式,返回適當的 Result:
// - add(a: f64, b: f64) -> Result<f64, CalculatorError>
// - subtract(a: f64, b: f64) -> Result<f64, CalculatorError>
// - multiply(a: f64, b: f64) -> Result<f64, CalculatorError>
// - divide(a: f64, b: f64) -> Result<f64, CalculatorError>
// - parse_and_calculate(expression: &str) -> Result<f64, CalculatorError>
use std::fs;
use std::io;
#[derive(Debug)]
enum FileProcessorError {
IoError(io::Error),
InvalidFormat,
EmptyFile,
}
impl From<io::Error> for FileProcessorError {
fn from(error: io::Error) -> Self {
FileProcessorError::IoError(error)
}
}
// 實作以下功能:
// - read_numbers_from_file(path: &str) -> Result<Vec<i32>, FileProcessorError>
// - calculate_statistics(numbers: &[i32]) -> Result<Statistics, FileProcessorError>
// - write_statistics_to_file(stats: &Statistics, path: &str) -> Result<(), FileProcessorError>
struct Statistics {
count: usize,
sum: i64,
average: f64,
min: i32,
max: i32,
}
// 建立一個完整的用戶管理系統,包含:
// - 自定義錯誤型別
// - 用戶註冊、登入、更新資料功能
// - 密碼驗證
// - 檔案持久化
// - 完整的錯誤處理
#[derive(Debug)]
pub enum UserError {
AlreadyExists(String),
NotFound(String),
InvalidPassword,
WeakPassword(String),
InvalidEmail(String),
StorageError(String),
}
// 不好:容易 panic
fn bad_example() {
let content = std::fs::read_to_string("config.txt").unwrap();
let number: i32 = content.parse().unwrap();
}
// 好:適當的錯誤處理
fn good_example() -> Result<i32, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string("config.txt")?;
let number: i32 = content.parse()?;
Ok(number)
}
// 不好:忽略錯誤
fn bad_example() {
let _ = std::fs::write("output.txt", "data"); // 錯誤被忽略
}
// 好:處理或傳播錯誤
fn good_example() -> Result<(), std::io::Error> {
std::fs::write("output.txt", "data")?;
Ok(())
}
// 不好:使用 String 作為錯誤型別
fn bad_error() -> Result<i32, String> {
Err("Something went wrong".to_string())
}
// 好:使用專門的錯誤型別
#[derive(Debug)]
enum MyError {
ParseError,
NetworkError,
}
fn good_error() -> Result<i32, MyError> {
Err(MyError::ParseError)
}
Result<T, E>
明確處理可能失敗的操作?
運算子簡化錯誤傳播,讓程式碼更簡潔unwrap
、expect
應該謹慎使用,主要用於確定不會失敗的情況unwrap_or
、map
、and_then
等方法提供函式式錯誤處理From
特徵讓錯誤型別轉換更方便