TL;DR

  • トレイトには、デフォルト実装を持つメソッド(と適切なトレイト境界)を足す。
  • こうすると、実装する人には最小限の API を、利用する人には便利で網羅的な API を提供できる。

内容

https://effective-rust.com/default-impl.html

実装する人と利用する人の対立

トレイトを設計するときは、トレイトを実装する人と利用する人の両方を考える必要がある。実装する人を楽にするには、目的を達成できる最小限のメソッドだけを用意すればよい。利用する人を楽にするには、あらゆるユースケースをカバーするメソッドを用意すればよい。この 2 つは対立する。

この対立は、デフォルト実装で和らげられる。たとえば、要素数が事前に分かるイテレータを表す ExactSizeIterator の is_empty は、len を使ったデフォルト実装を持つ。

fn is_empty(&self) -> bool {
    self.len() == 0
}

トレイトは、実装しなければならない少数の必須メソッドと、それ以外の多数のデフォルト実装を持つメソッドを持つのがよい

標準ライブラリでもこの形が広く使われている。Iterator の必須メソッドは next だけだが、デフォルト実装を持つメソッドは 50 を超える。また、トレイト境界とデフォルト実装をうまく組み合わせている。たとえば cloned は、要素が Clone を実装している場合だけ使える。

fn cloned<'a, T>(self) -> Cloned<Self>
where
    T: 'a + Clone,
    Self: Sized + Iterator<Item = &'a T>,
{
    Cloned::new(self)
}

リリース後のメソッド追加

トレイトには、最初のバージョンを公開した後でも、通常は新しいメソッドを安全に追加できる。「通常は」と付くのは、新しいメソッドの名前が、ある型が実装している別のトレイトのメソッドと衝突しうるからである。衝突してもコンパイルエラーにはならないが、名前解決の規則によって呼ばれるメソッドが変わる可能性がある。その場合、利用する人は <Concrete as Trait>::method() のような完全修飾構文で、どのメソッドを呼ぶかを明示する必要がある。

衝突しなければ、実装する人と利用する人の両方に対して後方互換性を保てる。

キーワード