Rust でエラー型に Clone が実装されていてほしい
これは物申す系の話とかではなく、単なる愚痴。 現状については現状セクション参照。
概要
-
エラー云々以前に、ユーザが後から (
Clone
等、自前でない trait の)実装を追加できない[0]以上、ライブラリ作者はできるだけ基本的な trait 群を derive しておくべきである。 - エラー型のオブジェクトを clone したい場合が存在する。
- error_chain crate でエラー型を用意すると、現状(0.11.0 ( リリース)時点)では
derive(Clone)
できない。
以上のような理由で、つらい。
エラーを clone したい状況
処理の完了や致命的な失敗の後にも、 Result を返す関数の呼び出しに制約をかけたくない場合
たとえば XML パーサである xml-rs crate 等が該当する。
xml::reader::EventReader::next()
は、(致命的な)パースエラー等があると Err
を返すが、エラーが返された後も next()
自体は制限なく何度でも呼び出すことができる。
If returned event is
XmlEvent::Error
orXmlEvent::EndDocument
, then further calls to this method will return this event again.
このような場合、最後のエラーを複数回返す必要が出てくるため、当然エラー型は Clone
を実装していてほしい。
以前、私が xml-rs を参考に書いた fbx_direct crate では、エラーとして I/O エラー (std::io::Error
) が有り得たため、自前で Clone
を実装することになってしまった。
Qiita の記事『Rustで std::io::Error をcloneしたいとき - Qiita』はこのときの副産物である。
完了や失敗の後の関数呼び出しに制約をかけるというのもひとつの選択で、たとえば futures-rs などはその選択を採っている。
Once a future has completed (returned
Ready
orErr
from poll), then any future calls topoll
may panic, block forever, or otherwise cause wrong behavior. TheFuture
trait itself provides no guarantees about the behavior ofpoll
after a future has completed.
poll
関数の項目の Panics セクションより。
強調は引用者による。
futures-rs の Future
trait 自体は各ライブラリ開発者が各々の型に対して実装しうるものであり、エラー型も様々だから、実装を単純にするためにこのように定めたのであろう。
エラー型を致命的でない警告に対して使いたい場合
コンセプト (サンプルコード)
致命的でない警告をエラーとして扱うかどうかユーザに委ねたい場合などがあり、この場合警告は Error
trait を実装していると良い。
コード例を書いたので参照されたい (playground へのリンク)。
// Buy only clean and not broken items.
assert_eq!(
Err(ShoppingError::Warning(ShoppingWarning::Dirty(2))),
buy(1, 2, |w| Err(w))
);
// Buy not broken items, allow dirty items.
assert_eq!(
Ok(2),
buy(1, 2, |w| {
if let ShoppingWarning::Broken = w {
Err(w)
} else {
Ok(())
}
})
);
// Buy not broken items, allow dirty items.
assert_eq!(
Err(ShoppingError::Warning(ShoppingWarning::Broken)),
buy(2, 2, |w| {
if let ShoppingWarning::Broken = w {
Err(w)
} else {
Ok(())
}
})
);
// Buy not broken items, allow dirty items.
// Log troubles.
let mut troubles = Vec::new();
assert_eq!(
Err(ShoppingError::Warning(ShoppingWarning::Broken)),
buy(2, 2, |w| {
troubles.push(w.clone());
if let ShoppingWarning::Broken = w {
Err(w)
} else {
Ok(())
}
})
);
assert_eq!(
troubles,
vec![
ShoppingWarning::Dirty(5),
ShoppingWarning::Broken,
]
);
警告のエラー型が Clone
を実装していてほしいのは、まさに最後の例のような場合である。
エラーをどこかに記憶(複製)したのち確認したい場合、 clone()
できてほしい[1]し、 assert_eq!()
等でテストをしたい場合には Clone
以外にも PartialEq
なども必要になるから、 Clone
に限らず基本的な trait はとにかく実装しておいてほしいということである。
具体例: fbxcel crate (自作)
先述の fbx_direct crate とは別に(というか改良して)、 fbxcel という crate を開発していて、こちらでは、致命的ではないがおかしいデータについて Warning
型で警告を発するようになっている。
単なるロギングではなく、こうして専用の型のついたオブジェクトを経由させることで、このライブラリを利用するアプリケーションからも警告履歴を利用可能となる。

具体例: fsck-xv6 (自作)
いつぞやの OS の授業の課題で xv6 のファイルシステムの validator (fsck-xv6) を実装したことがあり、このときもファイルシステムエラーを表現するための型に Error
trait を実装した。
こちらは一般的な意味での「エラー」ではあるが、ファイルシステムの妥当性検証という性質上、ファイルシステムのエラーがそのままプログラムの継続不可能を意味するわけではない。
よって、複数のエラーが発生すればそれらを全て列挙したり、或いは Vec
等のコンテナに溜めたりといった用途を想定することになる。
let errors = fs::validate(target_file)
.expect("Critical error happened and validation cannot be proceeded");
if errors.is_empty() {
println!("No errors detected.");
} else {
println!("Errors detected:");
for err in errors {
println!("{}", err);
}
}
main
関数内より抜粋
pub fn validate<P: AsRef<Path>>(path: P) -> io::Result<Vec<Error>> {
let path = path.as_ref();
let mut reader = BufReader::new(File::open(path)?);
let mut errors = vec![];
/// Validation error.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Error {
BitmapMismatch { target: BlockIndex, expected: bool },
DataBlockReferenceConflict { block: BlockIndex },
DirectoryWithoutDot { inode: Inode },
DirectoryWithoutDotdot { inode: Inode },
この設計は fbxcel のエラー処理の発想をそのまま使ったものであり、ゆえに結局警告をエラーとして使いたいのかそうでないのかよくわからない設計になっている。 今の私が同じような目的のコードを書いたら、最初の例のように特定の警告にフックをかけて処理を中断できるように作るはずである。
現状
ここまで熟々と書いてきたのは、つまるところエラーを clone したいというだけの話である。 現状でそれを妨げる要因は、以下のようなものである。
std::io::Error が Clone を実装していない
悲しいことに、そうなのである。
issue が立ってはいるが進展の様子はなく、当面は Qiita に書いたように誤魔化しつつやっていくしかない。
error-chain trait がエラー型に対する derive をサポートしていない
error-chain crate は、エラー型や関連する諸々を用意する際のボイラープレートを減らすためのライブラリである。
イマドキ手書きの温かみのあるエラー型を書いたりしないよね、 error-chain は人権だよねという空気がある[要出典]が、残念ながら error-chain で定義したエラー型は Clone
を実装していないのである!!!
()