Rust中错误处理的最简单指南

22-02-11 banq

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");
}

猜你喜欢