std::io::Error を複製したい
std::io::Error構造体は Clone trait を実装していないため、通常の手段では複製できない。
これが気になるのが、以下のようなケースだ。
/// ファイルになにかする系の自作ライブラリが出すエラー
# [derive(Debug)]
pub enum FileOpError {
CancelledByUser,
Io(std::io::Error),
LockedData,
}
ここで、自前で定義した FileOpError に Clone を自動で実装させたくとも、それができないのである。
では、どうしてもこいつを Clone させたい場合にどうするか。
以下のようにすれば良い。
impl Clone for FileOpError {
fn clone(&self) -> Self {
match *self {
FileOpError::CancelledByUser => FileOpError::CancelledByUser,
FileOpError::Io(ref err) => FileOpError::Io(std::io::Error::new(err.kind(), err.to_string())),
FileOpError::LockedData => FileOpError::LockedData,
}
}
}
std::io::Error の複製を自前で構築
唯一説明が必要であろう部分がこれだ。
std::io::Error::new(
err.kind(),
err.to_string())
std::io::Error::new()は、std::io::ErrorKindと別の Error trait を実装したオブジェクトから、新たに std::io::Error を作る。
ErrorKind は、std::io::Error::kind()で得られ、 Clone と Copy trait が実装されている。
inner error
io::Error::new() のもうひとつの引数であるが、そもそもこれはI/Oエラーの原因となったエラー、あるいはより低レベルで詳細を説明するエラー(これを inner error と呼ぶ)を渡すものだ。(逆にこれを io::Error から得るには、 std::io::Error::into_inner()等を使う。)
しかし、元のエラーを consume せずに inner error を得ても、この場面での利用は難しい(&Error までは取れるが、そこから Box<Error> を作れない)。
よって、本来の inner error の利用を諦め、代わりに文字列をエラー情報として持たせてやれば良い。
エラーを説明する文字列の取得は std::io::Error::description().to_string() で行える。
追記 2018-06-19: Error::description() は非推奨となり、 Display トレイトや、 Display を実装している型に対して間接的に自動で用意される ToString::to_string() を使いましょうということになっている。
参考: 文字列の lifetime と、エラーとしての利用
std::io::Error::new() が受け取るのは Into<Box<Error + Send + Sync>> である。
そしてstd::convert::Intoによれば、 "From<T> for U implies Into<U> for T" であり、std::convert::Fromによれば impl From<String> for Box<Error + Send + Sync> が実装されているから、結果として String を Box<Error + Send + Sync> に変換できるというわけである。
追記(2016/10/15): Rc や Arc を使う方法
io::Error の Clone についてはIssueが立ってはいる(Consider changing io::Error to use Arc so it can implement Clone · Issue #24135 · rust-lang/rust)が、進展のあった様子はない。
ここで言及されていることには、 std::io::Error は(inner errorも共に) Sync と Send traitを実装している。
すなわち、inner errorも io::Error も共にstd::sync::Arcを使って管理できるということである。
よって、直接に io::Error を持たず、その Arc を持てば、その Clone を使うことができる。
/// ファイルになにかする系の自作ライブラリが出すエラー
# [derive(Debug, Clone)]
pub enum FileOpError {
CancelledByUser,
Io(std::sync::Arc<std::io::Error>), // `Arc` を使う
LockedData,
}
マルチスレッドを全く考えないのであれば、勿論 Arc の代わりに std::rc::Rc を使っても良いだろう。
まとめ
- 自前で
Cloneもどきを実装するなら:-
io::Error::new()を使う。 - エラーの種類は
io::Error::kind()で得る。 - エラーの内容は完全には複製できないので、代わりに
format!()や.to_string()で得た文字列を使う。
-
-
RcやArcを使って良いなら:-
Arc<io::Error>のようにすれば、RcやArcのCloneをそのまま使える。
-
以上。