Rust 錯誤處理模式:Result、Option 與 ? 運算子
Rust 錯誤處理模式:Result、Option 與 ? 運算子
Rust 的錯誤處理是其最具特色的設計之一。沒有例外(Exception),沒有 null,取而代之的是 Result<T, E> 和 Option<T> 這兩個型別,強迫你在編譯時就處理所有可能的錯誤情況。一旦習慣了這套模式,你會發現它讓程式的可靠性大幅提升。
Option:處理可能不存在的值
Option<T> 替代了其他語言中的 null:
enum Option<T> {
Some(T),
None,
}fn find_user(id: u32) -> Option<String> {
let users = vec![
(1, "Alice".to_string()),
(2, "Bob".to_string()),
];
users.into_iter()
.find(|(uid, _)| *uid == id)
.map(|(_, name)| name)
}
fn main() {
match find_user(1) {
Some(name) => println!("找到使用者:{}", name),
None => println!("使用者不存在"),
}
// if let 語法(更簡潔)
if let Some(name) = find_user(2) {
println!("使用者:{}", name);
}
// unwrap_or:提供預設值
let name = find_user(999).unwrap_or("匿名".to_string());
println!("{}", name); // 匿名
// unwrap_or_else:惰性計算預設值
let name = find_user(999).unwrap_or_else(|| {
"Guest_".to_string() + &generate_id().to_string()
});
}Result:處理可能失敗的操作
enum Result<T, E> {
Ok(T),
Err(E),
}use std::fs;
use std::num::ParseIntError;
fn read_number_from_file(path: &str) -> Result<i32, String> {
let content = fs::read_to_string(path)
.map_err(|e| format!("讀取檔案失敗:{}", e))?;
let number = content.trim().parse::<i32>()
.map_err(|e| format!("解析數字失敗:{}", e))?;
Ok(number)
}
fn main() {
match read_number_from_file("number.txt") {
Ok(n) => println!("讀取到數字:{}", n),
Err(e) => eprintln!("錯誤:{}", e),
}
}? 運算子:優雅的錯誤傳播
? 是 Rust 最方便的語法糖,它等同於:
// 沒有 ? 的寫法
let result = some_function();
let value = match result {
Ok(v) => v,
Err(e) => return Err(e.into()),
};
// 有 ? 的寫法
let value = some_function()?;實際範例:
use std::fs;
use std::io;
use serde_json;
#[derive(Debug)]
struct Config {
host: String,
port: u16,
}
fn load_config(path: &str) -> Result<Config, Box<dyn std::error::Error>> {
let content = fs::read_to_string(path)?; // io::Error
let value: serde_json::Value = serde_json::from_str(&content)?; // serde_json::Error
let host = value["host"]
.as_str()
.ok_or("缺少 host 欄位")?
.to_string();
let port = value["port"]
.as_u64()
.ok_or("缺少 port 欄位")? as u16;
Ok(Config { host, port })
}自訂錯誤類型
使用 thiserror 套件(推薦)
use thiserror::Error;
#[derive(Debug, Error)]
enum AppError {
#[error("資料庫錯誤:{0}")]
Database(#[from] sqlx::Error),
#[error("驗證失敗:{field} - {message}")]
Validation {
field: String,
message: String,
},
#[error("找不到資源:{0}")]
NotFound(String),
#[error("未授權")]
Unauthorized,
}
async fn get_user(db: &Pool, id: i32) -> Result<User, AppError> {
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
.fetch_optional(db)
.await?; // sqlx::Error 自動轉換為 AppError::Database
user.ok_or_else(|| AppError::NotFound(format!("使用者 {} 不存在", id)))
}手動實作 Error trait
use std::fmt;
#[derive(Debug)]
struct CustomError {
message: String,
code: u32,
}
impl fmt::Display for CustomError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}] {}", self.code, self.message)
}
}
impl std::error::Error for CustomError {}組合 Option 和 Result
// Option 轉 Result
fn parse_age(s: Option<&str>) -> Result<u8, String> {
s.ok_or("缺少年齡欄位".to_string())?
.parse::<u8>()
.map_err(|e| format!("年齡格式錯誤:{}", e))
}
// 在 Iterator 中處理錯誤
fn parse_numbers(strings: Vec<&str>) -> Result<Vec<i32>, String> {
strings
.into_iter()
.map(|s| s.parse::<i32>().map_err(|e| format!("'{}' 不是有效數字: {}", s, e)))
.collect() // Vec<Result<i32, String>> → Result<Vec<i32>, String>
}
fn main() {
let numbers = parse_numbers(vec!["1", "2", "3"]);
println!("{:?}", numbers); // Ok([1, 2, 3])
let error = parse_numbers(vec!["1", "abc", "3"]);
println!("{:?}", error); // Err("'abc' 不是有效數字: ...")
}在 Web 框架中的錯誤處理(Axum)
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde_json::json;
// 讓自訂錯誤可以直接回傳 HTTP 回應
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match &self {
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "未授權".to_string()),
AppError::Validation { message, .. } => (StatusCode::BAD_REQUEST, message.clone()),
AppError::Database(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
"資料庫錯誤".to_string(),
),
};
(status, Json(json!({ "error": message }))).into_response()
}
}
// Handler 直接回傳 Result
async fn get_user_handler(
Path(id): Path<i32>,
State(db): State<Pool>,
) -> Result<Json<User>, AppError> {
let user = get_user(&db, id).await?;
Ok(Json(user))
}總結
Rust 的錯誤處理強迫你在編譯時就思考所有可能的失敗情況,雖然一開始會覺得繁瑣,但換來的是極高的程式可靠性。? 運算子大幅簡化了錯誤傳播的語法,搭配 thiserror 套件定義清晰的錯誤類型,可以寫出既安全又易讀的錯誤處理程式碼。
分享這篇文章