Help us understand the problem. What is going on with this article?

Rust のエラーまわりの変遷

Rust のエラーまわりの変遷

by legokichi
1 / 53

Rust LT #6 で発表したスライド

Error トレイトについて


std::error::Error トレイトとは


Error トレイト

こんなの

pub trait Error: Debug + Display {
    fn description(&self) -> &str;
    fn cause(&self) -> Option<&dyn std::error::Error>;
}

Error::description でエラーの内容を表示できる

println!("{}", err.description());

Error::cause でエラーの元を辿れる (cause chain)

let mut cause = err.cause();
while let Some(err) = cause {
  println!("{}", err.description());
  cause = err.cause();
}

Error トレイトの問題点


1. ErrorDebugDisplay トレイトを実装しないといけない

Error を derive できない(※当時は derive macro などなかった)


#[derive(Debug)]
enum MyError {
    Io(std::io::Error),
    Parse(std::num::ParseIntError),
}

// ボイラープレート
// pub trait Error: Debug + Display { ... }
impl std::error::Error for MyError {
    fn description(&self) -> &str {
        match *self {
            MyError::Io(ref err) => err.description(),
            MyError::Parse(ref err) => err.description(),
        }
    }
    fn cause(&self) -> Option<&std::error::Error> {
        match *self {
            MyError::Io(ref err) => Some(err),
            MyError::Parse(ref err) => Some(err),
        }
    }
}

impl std::fmt::Display for MyError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match *self {
            MyError::Io(ref err) => write!(f, "ファイル開けへんやんけ: {}", err),
            MyError::Parse(ref err) => write!(f, "パースできへんやんけ: {}", err),
        }
    }
}

2. スタックトレースがとれない

このエラーはどこから来たのかなんもわからん


3. Error::descriptionDisplay で役割が被ってる

// 何が違うの?
println!("{}", err.description());
println!("{}", err);

4. cause チェーンがイテラブルでない

// なぜそこで while
let mut cause = err.cause();
while let Some(err) = cause {
  cause = err.cause();
}

5. 元のエラー型にダウンキャストできない

// Error::cause の戻り値に 'static がついてない
let cause: Option<&Error> = err.cause();

6. Send も Sync も 'static ない

tokio などで使うのが大変


結論: Error トレイトは問題だらけなので……

  • error-chain
    • 問題だらけの Error を使うためのベストプラクティス
  • failure
    • そもそも Error なんて使わんきゃええ
  • RFC2504, "Fix the Error Trait"
    • いっそ Error を改善しよう

error-chain について


error-chain クレート


error_chain! マクロ

マクロで Error トレイトのボイラープレートを一括生成できる


error_chain!{
  types {  MyError, MyErrorKind, MyResultExt, MyResult; }
  links {
    Another(AnotherError, AnotherErrorKind);
  }
  foreign_links {
    Io(::std::io::Error);
  }
  errors {
    CannotOpenFile(path: String) {
      description("cannot open file")
      display("CannotOpenFile: '{}'", path)
    }
  }
}

この error_chain! マクロで生成されるコードは……


Error トレイトを実装した MyError 構造体

pub struct MyError { ... }
impl MyError { ... }
// Error トレイト の実装
impl Error for MyError { ... }
impl Debug for MyError { ... }
impl Display for MyError { ... }

エラー原因を保持するための MyErrorKind 列挙体

pub enum MyErrorKind {
  Msg(String),
  Another(AnotherErrorKind),
  Io(::std::io::Error),
  CannotOpenFile(path: String),
}
impl MyErrorKind { ... }
impl Debug for MyErrorKind { ... }
impl Display for MyErrorKind { ... }

error_chain::ChainedError 拡張トレイトの実装

impl error_chain::ChainedError for MyError { ... }

ChainedErrorResult で使えるようにする ResultExt トレイトの実装

pub trait MyResultExt<T> { ... }
pub type MyResult<T> = Result<T, MyError>;

From トレイトの実装

impl From<MyErrorKind> for MyError { ... }
impl<'a> From<&'a str> for MyError { ... }
impl From<String> for MyError { ... }
impl From<another_errors::Error> for MyError { ... }
impl<'a> From<&'a str> for MyErrorKind { ... }
impl From<String> for MyErrorKind { ... }
impl From<MyError> for MyErrorKind  { ... }
impl From<another_errors::ErrorKind> for MyErrorKind { ... }

この膨大なコードのおかげで……


1. enum ErorrKind に原因のエラーを持てる

pub enum MyErrorKind {
  Msg(String),
  Another(AnotherErrorKind),
  Io(::std::io::Error),
  CannotOpenFile(path: String),
}

2. エラーチェーンを積める

use std::fs::File;
use errors::{MyError, MyErrorKind, MyResultExt};
try!(File::open("foo.txt")
  .map_err(MyErrorKind::Io)
  .chain_err(|| "ファイル開けへんやんけ"));
  .chain_err(|| MyErrorKind::CannotOpenFile("foo.txt".to_string)));
// CannotOpenFile -> Msg -> Io -> std::io::Error

3. MyError::iter で原因をイテレートできる

for err in err.iter() {
  println!("{}", err);
}

4. MyError::backtrace でバックトレースがとれる

// need RUST_BACKTRACE=1
if let Some(trace) = err.backtrace() {
  // io::Error ではなく MyError が作られた時点のトレースが得られる
  println!("{:?}", trace);
}

error-chain の問題点

  • derive macro なんてなかった
  • 生成されるコードが 膨大
  • 初見殺し
  • 生成される MyError!Sync で使い勝手が悪い

結論: error-chain は過去の遺産


じゃあ failure を使えばいいのか?


failure について


failure クレート

  • 2017 年 11 月に登場
  • 問題だらけの Error トレイトを置き換えるために開発された
  • Error トレイトの代わりに Fail トレイトを導入
  • Box<dyn Error> の代わりに failure::Error 構造体を導入

derive できる

#[derive(Debug, Fail)]
pub enum MyError {
  #[fail(display = "Input was invalid UTF-8 at index {}", _0)]
  Utf8Error(usize),
  #[fail(display = "IoError: {}", _0)]
  Io(#[cause] io::Error),
}

cause chain がイテラブル

impl Fail {
  pub fn iter_causes(&self) -> Causes { ... }
  ...
}

backtrace が取れる

pub trait Fail: Display + Debug + Send + Sync + 'static {
  fn backtrace(&self) -> Option<&Backtrace> { ... }
  ...
}

FailSend + Sync + 'static がついてる

tokio でも使える

pub trait Fail: Display + Debug + Send + Sync + 'static {
  ...
}

ダウンキャストできる

Fail'static がついてるので

pub trait Fail: Display + Debug + Send + Sync + 'static {
  ...
}
impl Fail {
  pub fn downcast_ref<T: Fail>(&self) -> Option<&T> { ... }
  pub fn downcast_mut<T: Fail>(&mut self) -> Option<&mut T> { ... }
  ...
}

cause chain を積める

err
 .context(format_err!("Error code: なんかエラーおきた}"))
 .context("なんかエラーおきた".to_string());

結論: failure はすごくいいが……


RFC 2504, "Fix the Error trait"


新しい Error トレイト


新しい Error トレイト

  • backtrace できる
  • ダウンキャストできる
trait Error: Display + Debug {
    fn backtrace(&self) -> Option<&Backtrace>;
    fn source(&self) -> Option<&dyn Error + 'static>;
}

新しい Error トレイト

以前のメソッドがひとつも残ってない

trait Error: Display + Debug {
    fn backtrace(&self) -> Option<&Backtrace>;
    fn source(&self) -> Option<&dyn Error + 'static>;
}

未解決の問題

  • Backtrace の具体的な API が未定
  • イテラブルな cause chain の API が未定
  • derive(Error) できない

2019年6月現在


error-chain は開発停止


failure は凍結

  • メジャーアップデートはは実装が落ち着くまで停止中 (まるで futures-0.1 のよう?)
    • Fail -はErrorExt にして新 Error トレイトを継承するかも
    • failure::Errorfailure::DefaultError になるかも

ポスト failure が増殖中

などなど


おわり


Appendix. Rust のエラーまわりの歴史

2014

2015

2016

2017

2018

2019

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした