iT邦幫忙

2024 iThome 鐵人賽

DAY 6
1
Software Development

Rust 學得動嗎系列 第 6

[Day 6] Rust 的錯誤處理:優雅地管理失敗

  • 分享至 

  • xImage
  •  

今天,我們來聊聊 Rust 的錯誤處理機制。Rust 的錯誤處理設計旨在幫助開發者寫出更加穩健和可靠的程式。

可恢復錯誤與 Result<T, E>

Rust 將錯誤分為可恢復和不可恢復兩種。對於可恢復錯誤,Rust 提供了 Result<T, E> 枚舉:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

使用 Result 的例子:

use std::fs::File;

fn main() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => {
            panic!("無法打開文件:{:?}", error)
        },
    };
}

使用 ? 運算符號簡化錯誤處理

? 運算符號可以大大簡化錯誤處理的程式碼:

use std::fs::File;
use std::io;
use std::io::Read;

fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

自定義錯誤類型

我們可以建立自己的錯誤類型來更好地表達程式中可能出現的錯誤:

use std::fmt;

#[derive(Debug)]
struct AppError {
    kind: String,
    message: String,
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}:{}", self.kind, self.message)
    }
}

impl std::error::Error for AppError {}

使用 thiserror 簡化錯誤定義

thiserror lib可以幫助我們更容易地定義錯誤類型:

use thiserror::Error;

#[derive(Error, Debug)]
enum DataStoreError {
    #[error("找不到資料")]
    NotFound,
    #[error("無訪問權限")]
    Unauthorized,
    #[error("無效的輸入:{0}")]
    InvalidInput(String),
}

錯誤轉換

有時我們需要將一種錯誤類型轉換為另一種:

use std::fs;
use std::io;

fn read_username_from_file() -> Result<String, io::Error> {
    fs::read_to_string("hello.txt")
}

fn get_username() -> Result<String, String> {
    read_username_from_file().map_err(|e| e.to_string())
}

不可恢復錯誤與 panic!

對於不可恢復的錯誤,Rust 提供了 panic!

fn main() {
    panic!("發生了嚴重錯誤!");
}

使用 unwrap 和 expect

unwrapexpect 方法可以在錯誤發生時快速 panic:

use std::fs::File;

fn main() {
    let f = File::open("hello.txt").unwrap();
    let f = File::open("hello.txt").expect("無法打開文件");
}

實際應用範例

讓我們看一個更複雜的例子,看看如何在實際應用中處理錯誤:

Cargo.toml

[dependencies]
rand = "0.8.5"
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0.63"
serde_json = "1.0.128"

main.rs

use std::fs::File;
use std::io::{self, Read};
use serde::Deserialize;
use thiserror::Error;

#[derive(Deserialize, Debug)]
struct Config {
    server: String,
    port: u16,
    // 其他...
}

#[derive(Error, Debug)]
enum ConfigError {
    #[error("無法讀取設定文件")]
    ReadError(#[from] io::Error),
    #[error("設定文件格式無效")]
    ParseError(#[from] serde_json::Error),
}

fn read_config(path: &str) -> Result<Config, ConfigError> {
    let mut file = File::open(path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    let config: Config = serde_json::from_str(&contents)?;
    Ok(config)
}

fn main() {
    match read_config("config.json") {
        Ok(config) => println!("成功讀取設定:{:?}", config),
        Err(e) => eprintln!("讀取設定失敗:{}", e),
    }
}

讀取失敗
https://ithelp.ithome.com.tw/upload/images/20240920/20140358ScaveqVDoS.png

新增 config.json

{
  "server": "localhost",
  "port": 25525
}

讀取成功
https://ithelp.ithome.com.tw/upload/images/20240920/20140358eFvIr2Nhy6.png

明天,我們來探討 Rust 的泛型和 trait,這些概念允許我們編寫更加靈活和可重用的程式。


上一篇
[Day 5] Rust 生命週期:確保引用有效性的關鍵
下一篇
[Day 7] Rust 的泛型和 trait:實現靈活且可重用的程式碼
系列文
Rust 學得動嗎13
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言