在上一篇中,我們深入了解了 Borrow Checker 如何成為最嚴格的 Code Reviewer。
現在,讓我們轉向另一個重要的主題:錯誤處理。
在傳統的程式語言中,錯誤處理往往是一個容易被忽略的問題。
我們可能忘記檢查返回值,或者讓異常靜默地傳播。
Rust 的 Result
和 Option
類型提供了一種全新的錯誤處理方式:強制性的錯誤處理。
Rust 的 Result
和 Option
類型提供了一種全新的錯誤處理方式:
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("division by zero".to_string())
} else {
Ok(a / b)
}
}
fn main() {
match divide(10, 0) {
Ok(result) => println!("Result: {}", result),
Err(error) => println!("Error: {}", error),
}
}
關鍵在於:你必須處理 Result
的兩種情況,編譯器不會讓你忽略錯誤。
fn find_index(slice: &[i32], target: i32) -> Option<usize> {
for (index, &value) in slice.iter().enumerate() {
if value == target {
return Some(index);
}
}
None
}
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
match find_index(&numbers, 3) {
Some(index) => println!("Found at index: {}", index),
None => println!("Not found"),
}
}
同樣地,你必須處理 Option
的兩種情況。
跟其他程式語言的錯誤處理對比
Rust 的 Result
類型提供了豐富的鏈式操作方法:
?
運算符fn read_file(path: &str) -> Result<String, std::io::Error> {
std::fs::read_to_string(path)
}
fn process_file(path: &str) -> Result<String, String> {
let content = read_file(path)?; // 如果出錯,直接返回
Ok(content.to_uppercase())
}
fn main() {
match process_file("example.txt") {
Ok(result) => println!("{}", result),
Err(error) => println!("Error: {}", error),
}
}
?
運算符讓錯誤處理變得更加簡潔。
map
和 and_then
fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
s.parse()
}
fn double(n: i32) -> i32 {
n * 2
}
fn main() {
let result = "42"
.parse::<i32>()
.map(double)
.map(|n| n.to_string());
match result {
Ok(s) => println!("Result: {}", s),
Err(e) => println!("Error: {}", e),
}
}
這些方法讓錯誤處理變得更加函式式。
Rust 的 Option
類型提供了安全的空值處理:
map
和 unwrap_or
fn find_user(id: u32) -> Option<String> {
if id == 1 {
Some("Alice".to_string())
} else {
None
}
}
fn main() {
let user = find_user(1)
.map(|name| format!("Hello, {}!", name))
.unwrap_or("User not found".to_string());
println!("{}", user);
}
match
進行模式匹配fn process_option(opt: Option<i32>) {
match opt {
Some(value) => println!("Got value: {}", value),
None => println!("No value"),
}
}
fn process_data(data: &str) -> Result<String, String> {
if data.is_empty() {
Err("empty data".to_string())
} else {
Ok(data.to_uppercase())
}
}
fn main() {
match process_data("") {
Ok(result) => println!("{}", result),
Err(error) => println!("Error: {}", error),
}
}
理解了 Result
和 Option
的哲學後,我們可以設計出更安全的 API:
// 糟糕的設計:可能返回 None 但沒有明確表示
fn get_user_name(id: u32) -> String {
if id == 1 {
"Alice".to_string()
} else {
"".to_string() // 空字串表示沒有找到
}
}
// 好的設計:明確表示可能失敗
fn get_user_name(id: u32) -> Option<String> {
if id == 1 {
Some("Alice".to_string())
} else {
None
}
}
Rust 允許你定義自己的錯誤類型:
#[derive(Debug)]
enum MyError {
DivisionByZero,
InvalidInput(String),
IoError(std::io::Error),
}
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
MyError::DivisionByZero => write!(f, "Division by zero"),
MyError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
MyError::IoError(e) => write!(f, "IO error: {}", e),
}
}
}
impl std::error::Error for MyError {}
fn divide(a: i32, b: i32) -> Result<i32, MyError> {
if b == 0 {
Err(MyError::DivisionByZero)
} else {
Ok(a / b)
}
}
Rust 的 Result
和 Option
類型代表了一種錯誤處理哲學的轉變:
拒絕「忽略錯誤」的這種嚴格性,讓我們能夠編寫出更加可靠和安全的程式碼。
在下一章中,我們將深入探討智慧指標,看看 Rust 如何用 Box
、Rc
、Arc
來處理共享所有權。