ジェネリクスとトレイトの組み合わせでconflictが発生する場合の回避策

やりたいこと

Result<T>と、Result<Option<T>>な型に対して、オリジナルのtraitを実装して、それぞれ処理を切り分けたい。

問題

シンプルに考えると、以下のようなコードになる。

trait SomeTrait {
    fn action(self) -> String;
}

// 課題:以下の2つのトレイトの実装が同じものとして扱われてしまって、コンパイルエラーになる。
//    このコンパイルエラーを解決したい。
impl <T> SomeTrait for Result<Option<T>, ()> {
    fn action(self) -> String {
        "Result<Option<T>>".to_string()
    }
}

impl <T> SomeTrait for Result<T, ()> {
    fn action(self) -> String {
        "Result<T>".to_string()
    }
}

#[cfg(test)]
mod test {
    use crate::{SomeTrait};
    #[test]
    fn test1() {
        assert_eq!("Result<T>".to_string(), Ok(1).action());
        assert_eq!("Result<Option<T>>".to_string(), Ok(Some(1)).action());
    }
}

コメントにも書いているけど、このコードをコンパイルするとエラーになる。

error[E0119]: conflicting implementations of trait `SomeTrait` for type `std::result::Result<std::option::Option<_>, ()>`:
  --> examples/pol2_sample.rs:14:1
   |
8  | impl <T> SomeTrait for Result<Option<T>, ()> {
   | -------------------------------------------- first implementation here
...
14 | impl <T> SomeTrait for Result<T, ()> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `std::result::Result<std::option::Option<_>, ()>`

原因

これは、そもそもResult<T>のジェネリクス(T)自体が、Option<T>にもマッチしてしまうため。

回避案

ジェネリクスT自体が、Optionと区別できれば解決できる。 ということで、まずはジェネリクスTとして扱いたい各型で、適当に宣言した空のtrait(今回はEmptyという名前にした)を実装する。

そうすると、関数のジェネリクス境界で、T型はその空のEmptyトレイトを実装しているもの、という指定が出来る。 Optionはその空のEmptyトレイトを実装していないので、Result<T>TOption<T>として扱われることを防げる。

実装は以下の通り。

fn main() {}

trait SomeTrait {
    fn action(self) -> String;
}

// 適当な空のtraitを宣言
trait Empty{}

// ジェネリクスTとして使いたい型でそのtraitを実装。
impl Empty for i32 {}

// ジェネリクス境界を指定することで、Option自体がTと誤解されることを防げる。
impl <T> SomeTrait for Result<Option<T>, ()> where T: Empty {
    fn action(self) -> String {
        "Result<Option<T>>".to_string()
    }
}

// こちらも同様に実装
impl <T> SomeTrait for Result<T, ()> where T: Empty {
    fn action(self) -> String {
        "Result<T>".to_string()
    }
}

#[cfg(test)]
mod test {
    use crate::{SomeTrait};
    #[test]
    fn test1() {
		// 同じactionメソッドでもそれぞれ処理が切り分けられた。
        assert_eq!("Result<T>".to_string(), Ok(1).action());
        assert_eq!("Result<Option<T>>".to_string(), Ok(Some(1)).action());
    }
}

まとめ

このやり方が推奨されるような解決方法かどうかはわかりませんが、少なくとも自分のユースケースではこれで問題ないと思っています。 流石にEmptyと言う名前は返る必要がありますが。。。

公開日:2020/12/22

Rust

About me

ドイツの現地企業でWeb Developer/System Administratorとして働いているアラフォーおじさんです。

プログラミングとかコンピュータに関する事がメインですが、日常的なメモとか雑多なことも書きます。

Links :
目次

やりたいこと


問題


原因


回避案


まとめ