何とは言わない天然水飲みたさ

Rust でエラー型に Clone が実装されていてほしい

現状セクション参照。

概要

以上のような理由で、つらい。

エラーを clone したい状況

xml::reader::EventReader::next() は、(致命的な)パースエラー等があると Err を返すが、エラーが返された後も next() 自体は制限なく何度でも呼び出すことができる。

If returned event is XmlEvent::Error or XmlEvent::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 or Err from poll), then any future calls to poll may panic, block forever, or otherwise cause wrong behavior. The Future trait itself provides no guarantees about the behavior of poll after a future has completed.

futures-rs の Future trait 自体は各ライブラリ開発者が各々の型に対して実装しうるものであり、エラー型も様々だから、実装を単純にするためにこのように定めたのであろう。

エラー型を致命的でない警告に対して使いたい場合

コード例を書いたので参照されたい (playground へのリンク)。

    // Buy only clean and not broken items.
    assert_eq!(
        Err(ShoppingError::Warning(ShoppingWarning::Dirty(2))),
        buy(1, 2, |w| Err(w))
    );
商品が汚れていたり壊れていたら買い物をやめる例 (ソースコード より抜粋, playground へのリンク)。
    // 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(())
            }
        })
    );
商品が汚れていても買うが、壊れていたら買い物をやめる例 (ソースコード より抜粋, playground へのリンク)。
    // 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,
        ]
    );
商品が汚れていても買うが、壊れていたら買い物をやめ、それはさておきトラブルを外部に記録しておく例 (ソースコード より抜粋, playground へのリンク)。

警告のエラー型が Clone を実装していてほしいのは、まさに最後の例のような場合である。 エラーをどこかに記憶(複製)したのち確認したい場合、 clone() できてほしい[1]し、 assert_eq!() 等でテストをしたい場合には Clone 以外にも PartialEq なども必要になるから、 Clone に限らず基本的な trait はとにかく実装しておいてほしいということである。

具体例: fbxcel crate (自作)