トレイトにより型に同名のメソッドが複数ある場合について。

メソッドの呼出記法

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

通常記法

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

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

完全修飾記法

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

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

トレイトどうしでの衝突

候補がどれもトレイト由来の場合、通常記法ではエラーになる。

サンプル

以下では、型 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");
   |                ~~~~~~~~~~~~~~~~~~~~~~~

型とトレイトでの衝突

候補に型由来のものがあれば、通常記法ではそれが優先される。

サンプル

以下では、型 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"
    }
}

破滅的な問題

この仕様はクレートのアップデートで型にメソッドが追加され、運悪く既存のものと引数などが同じだった場合、こっそりと呼出先が変更される大問題になりうる。

対症療法

クレートの利用側で完全修飾構文を使えば、この問題を防げる。

しかし、クレートの定義側での対策はできない。また、必要な全ての箇所 (型を経由したトレイトメソッドの呼出箇所) でこれを行うと、地味に面倒でコードも長くなってしまう。

公式の見解

Cargo における SemVer 互換性の解説を見ると、これは意図された設計らしい。