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
をそのまま使える。
-
以上。