LoginSignup
9
6

More than 3 years have passed since last update.

Rustで std::io::Error をcloneしたいとき

Last updated at Posted at 2016-07-03

std::io::Error を複製したい

std::io::Error構造体は Clone trait を実装していないため、通常の手段では複製できない。
これが気になるのが、以下のようなケースだ。

自作エラー型の例
/// ファイルになにかする系の自作ライブラリが出すエラー
#[derive(Debug)]
pub enum FileOpError {
    CancelledByUser,
    Io(std::io::Error),
    LockedData,
}

ここで、自前で定義した FileOpErrorClone を自動で実装させたくとも、それができないのである。
では、どうしてもこいつを 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 の複製を自前で構築

唯一説明が必要であろう部分がこれだ。

IOエラーを複製する
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()で得られ、 CloneCopy 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> が実装されているから、結果として StringBox<Error + Send + Sync> に変換できるというわけである。

追記(2016/10/15): RcArc を使う方法

io::ErrorClone についてはIssueが立ってはいる(Consider changing io::Error to use Arc so it can implement Clone · Issue #24135 · rust-lang/rust)が、進展のあった様子はない。
ここで言及されていることには、 std::io::Error は(inner errorも共に) SyncSend 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() で得た文字列を使う。
  • RcArc を使って良いなら:
    • Arc<io::Error> のようにすれば、 RcArcClone をそのまま使える。

以上。

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6