Rust中錯誤處理的最簡單指南

banq發表於2022-02-11

Rust 中有兩種型別的錯誤:
  • 不可恢復的錯誤(例如,未檢查的越界陣列訪問)
  • 可恢復的錯誤(例如,功能失敗)

 
不可恢復的錯誤
對於無法處理且會使您的程式進入不可恢復狀態的錯誤,我們使用panic! 宏。

fn encrypt(key: &[u8], data: &[u8]) -> Vec<u8> {
  if key.len() != 32 {
    panic!("encrypt: key length is invalid");
  }
  // ...
}

另一種觸發 a 的方法panic是使用assert! 宏。

fn encrypt(key: &[u8], data: &[u8]) -> Vec<u8> {
  assert!(key.len() == 32, "encrypt: key length is invalid");
  // ...
}

話雖如此,在 Rust 中處理錯誤非常符合人體工程學,所以我認為沒有充分的理由去故意panic.


可恢復的錯誤
應處理的錯誤將與Result 列舉一起返回。

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

例如:

// Here, our error type is `String`
fn ultimate_answer(guess: i64) -> Result<(), String> {
  if guess == 42 {
    return Ok(());
  }
  return Err("Wrong answer".to_string());
}

現在,將 aString作為錯誤返回並不是很有用。事實上,同一個函式可能會返回許多不同的錯誤,因此越來越難以精確處理它們:

fn ultimate_answer(guess: i64) -> Result<(), String> {
  if guess == 42 {
    return Ok(());
  } else if guess > 39 && guess <= 41 {
      return Err("A little bit more".to_string());
  } else if guess <= 45 && guess > 42 {
    return Err("A little bit less".to_string());
  }
  return Err("Wrong answer".to_string());
}

或者,許多不同的函式可以返回相同的錯誤:

fn do_something() -> Result<(), String> {
  // ...
  return Err("Something went wrong".to_string());
}

fn do_something_else() -> Result<(), String> {
  // ...
  return Err("Something went wrong".to_string());
}

fn do_another_thing() -> Result<(), String> {
  // ...
  return Err("Something went wrong".to_string());
}

這是我們需要定義自己的Error列舉的地方。通常,我們Error透過 crate 定義 1 個列舉。

pub enum Error {
  WrongAnswer,
  More,
  Less,
}

fn ultimate_answer(guess: i64) -> Result<(), Error> {
  if guess == 42 {
    return Ok(());
  } else if guess > 39 && guess <= 41 {
      return Err(Error::More);
  } else if guess <= 45 && guess > 42 {
    return Err(Error::Less);
  }
  return Err(Error::WrongAnswer);
}

然後,我們可能希望為每個錯誤案例標準化錯誤訊息。為此,社群選擇了thiserror crate。

#[derive(thiserror::Error)]
pub enum Error {
  #[error("Wrong answer")]
  WrongAnswer,
  #[error("A little bit more")]
  More,
  #[error("A little bit less")]
  Less,
}

多虧了thiserror::Error,您的Error列舉現在實現了std::error::Error特徵,因此也實現了Debug和Display特徵。

然後我們可以處理一個潛在的錯誤match。

fn question() -> Result<(), Error> {
  let x = // ...
  match ultimate_answer(x) {
    Ok(_) => // do something
    Err(Error::More) => // do something
    Err(Error::Less) => // do something
    Err(Error::WrongAnswer) => // do something
  }
  // ...
}

或者,處理錯誤的最常見方法是使用?.

fn question() -> Result<(), Error> {
  let x = // ...
  ultimate_answer(x)?; // if `ultimate_answer` returns an error, `question` stops here and returns the error.
  // ...
}

這是一個快捷方式:

fn question() -> Result<(), Error> {
  let x = // ...
  match ultimate_answer(x) {
    Ok(_) => {},
    Err(err) => return Err(err.into()),
  };
  // ...
}
 


 

錯誤轉換
您的程式或庫可能會使用許多依賴項,每個依賴項都有自己的錯誤型別,但為了能夠使用?,您的Error型別需要為依賴項的錯誤型別實現From特徵。

#[derive(Error, Debug, Clone)]
pub enum Error {
    #[error("Internal error.")]
    Internal(String),
    #[error("Not found.")]
    NotFound,
    #[error("Permission Denied.")]
    PermissionDenied,
    #[error("Invalid argument: {0}")]
    InvalidArgument(String),
}

impl std::convert::From<std::num::ParseIntError> for Error {
    fn from(err: std::num::ParseIntError) -> Self {
        Error::InvalidArgument(err.to_string())
    }
}

impl std::convert::From<sqlx::Error> for Error {
    fn from(err: sqlx::Error) -> Self {
        match err {
            sqlx::Error::RowNotFound => Error::NotFound,
            _ => Error::Internal(err.to_string()),
        }
    }
}



最後,您可以使用.unwrap()和.expect()對可恢復的錯誤感到恐慌

fn do_something() -> Result<(), Error> {
  // ...
}

fn main() {
  // panic if do_something returns Err(_)
  do_something().unwrap();
}

// or

fn main() {
  // panic if do_something returns Err(_) with the message below
  do_something().expect("do_something returned an error");
}

相關文章