トレイトどうし又はトレイトと型に同名のメソッドがあると、それらの衝突がありうる。

ここでは衝突時におこる問題とその対処法について解説している。

基礎知識

メソッドの呼出記法には二通りある。

通常記法

例: target.method(arg1, arg2, ...)

短く手軽だが、どのトレイトのメソッドか曖昧になりうる記法。

完全修飾記法

例: SomeTrait::method(&target, arg1, arg2, ...)

長く煩雑だが、どのトレイトのメソッドか明確になる記法。

型 vs トレイト

同名のメソッドに型由来のものがあれば、通常記法ではそれが優先される。

サンプル

以下では、型 MyType がトレイト MyTrait を実装しており、そのどちらもがメソッド method を持つ。この場合、通常記法による呼出では前者のメソッドが優先される。


fn main() {
    let target = MyType();
    assert_eq!(target.method(), "MyType");
    assert_eq!(MyType::method(&target), "MyType");
    assert_eq!(MyTrait::method(&target), "MyTrait");
}

trait MyTrait {
    fn method(&self) -> &str;
}

struct MyType();

impl MyTrait for MyType {
    fn method(&self) -> &str {
        "MyTrait"
    }
}

impl MyType {
    pub fn method(&self) -> &str {
        "MyType"
    }
}

注意点

上流クレートで追加したメソッドが下流クレートの呼出箇所で衝突する危険性について。

引数型などが一致しない場合、コンパイルエラーになる!

引数型などが一致した場合、静かに呼出先が変更される!!

対策

残念ながらあまり強力な方法はない…。

上流側: 困難 (強いて言うなら下流側の努力を祈るくらい…)

下流側: 完全修飾構文の使用 (地味にコードが長く読みにくくなる…)

トレイト vs トレイト

同名のメソッドがどちらもトレイト由来の場合、通常記法ではエラーになる。

サンプル

以下では、型 MyType は二つのトレイト TraitXTraitY を実装しており、そのどちらもがメソッド method を持つ。この場合、通常記法による呼出ではエラーになる。


fn main() {
    let target = MyType();
    assert_eq!(target.method(), "XXX");
    assert_eq!(TraitX::method(&target), "TraitX");
    assert_eq!(TraitY::method(&target), "TraitY");
}

struct MyType();
impl TraitX for MyType {}
impl TraitY for MyType {}

trait TraitX {
    fn method(&self) -> &str {
        "TraitX"
    }
}

trait TraitY {
    fn method(&self) -> &str {
        "TraitY"
    }
}

error[E0034]: multiple applicable items in scope
  --> src/main.rs:3:20
   |
3  |     assert_eq!(target.method(), "XXX");
   |                       ^^^^^^ multiple `method` found
   |
note: candidate #1 is defined in an impl of the trait `TraitX` for the type `MyType`
  --> src/main.rs:13:5
   |
13 |     fn method(&self) -> &str {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^
note: candidate #2 is defined in an impl of the trait `TraitY` for the type `MyType`
  --> src/main.rs:19:5
   |
19 |     fn method(&self) -> &str {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^
help: disambiguate the method for candidate #1
   |
3  |     assert_eq!(TraitX::method(&target), "XXX");
   |                ~~~~~~~~~~~~~~~~~~~~~~~
help: disambiguate the method for candidate #2
   |
3  |     assert_eq!(TraitY::method(&target), "XXX");
   |                ~~~~~~~~~~~~~~~~~~~~~~~

注意点

上流クレートで追加したメソッドが下流クレートの呼出箇所で衝突する危険性について。

型がまざる場合とは異なり、トレイトどうしの場合は一律でコンパイルエラーになる。

対策

型がまざる場合よりやや状況はよい。

上流側: 型にトレイトのメソッドをコピー (よく使う型ではかなり有効)

下流側: 完全修飾構文の使用 (こちらは型が混ざる場合と同様)

補足

型にトレイトのメソッドをコピーする手法について。

これは『型 vs トレイト』で紹介した型優先のルールの応用である。

例: Range 型には RangeBounds トレイト由来の contains メソッド、そして型それ自身の contains メソッドの両方がある。後者は前者への中継のみを行うメソッドであり、一見するとこれは無意味だが、これによりどんなトレイト実装が適用されているかを気にせず Rangecontains を通常記法で一意に呼び出せるようになる。

関連仕様

Cargo における SemVer 互換性の解説より。

ここで紹介したメソッド追加の変更は「許容されうる破壊的変更」扱いらしい。