付録C: 継承可能なトレイト

本のいろんな箇所でderive属性について議論しました。これは構造体や、enum定義に適用できます。 derive属性は、derive記法で注釈した型に対して独自の規定の実装でトレイトを実装するコードを生成します。

この付録では、標準ライブラリのderiveと共に使用できる全トレイトの参照を提供します。各節は以下を講義します:

  • このトレイトを継承する演算子やメソッドで可能になること
  • deriveが提供するトレイトの実装がすること
  • トレイトを実装することが型についてどれほど重要か
  • そのトレイトを実装できたりできなかったりする条件
  • そのトレイトが必要になる処理の例

derive属性が提供する以外の異なる振る舞いが欲しいなら、それらを手動で実装する方法の詳細について、 各トレイトの標準ライブラリのドキュメンテーションを調べてください。

標準ライブラリで定義されている残りのトレイトは、deriveで自分の型に実装することはできません。 これらのトレイトには知覚できるほどの規定の振る舞いはないので、自分が達成しようしていることに対して、 道理が通る方法でそれらを実装するのはあなた次第です。

継承できないトレイトの例はDisplayで、これはエンドユーザ向けのフォーマットを扱います。常に、エンドユーザ向けに型を表示する適切な方法について、 考慮すべきです。型のどの部分をエンドユーザは見ることができるべきでしょうか?どの部分を関係があると考えるでしょうか? どんな形式のデータがエンドユーザにとって最も関係があるでしょうか?Rustコンパイラには、 この見識がないため、適切な規定動作を提供してくれないのです。

この付録で提供される継承可能なトレイトのリストは、包括的ではありません: ライブラリは、自身のトレイトにderiveを実装でき、 deriveと共に使用できるトレイトのリストが実に限りのないものになってしまうのです。deriveの実装には、 プロシージャルなマクロが関連します。マクロについては、付録Dで講義します。

プログラマ用の出力のDebug

Debugトレイトにより、フォーマット文字列でのデバッグ成形が可能になり、 {}プレースホルダー内に:?を追記することで表します。

Debugトレイトにより、デバッグ目的で型のインスタンスを出力できるようになるので、あなたや型を使用する他のプログラマが、 プログラムの実行の特定の箇所でインスタンスを調べられます。

Debugトレイトは、例えば、assert_eq!マクロを使用する際などに必要になります。 このマクロは、プログラマがどうして2つのインスタンスが等価でなかったのか確認できるように、 等価アサートが失敗したら、引数として与えられたインスタンスの値を出力します。

等価比較のためのPartialEqEq

PartialEqトレイトにより、型のインスタンスを比較して、等価性をチェックでき、==!=演算子の使用を可能にします。

PartialEqを継承すると、eqメソッドを実装します。構造体にPartialEqを継承すると、 フィールドが等しい時のみ2つのインスタンスは等価になり、いずれかのフィールドが等価でなければ、 インスタンスは等価ではなくなります。enumに継承すると、各列挙子は、自身には等価ですが、他の列挙子には等価ではありません。

PartialEqトレイトは例えば、assert_eq!マクロを使用する際に必要になります。 これは、等価性のためにとある型の2つのインスタンスを比較できる必要があります。

Eqトレイトにはメソッドはありません。その目的は、注釈された型の全値に対して、値が自身と等しいことを通知することです。 Eqトレイトは、PartialEqを実装する全ての型がEqを実装できるわけではないものの、 PartialEqも実装する型に対してのみ適用できます。これの一例は、浮動小数点数型です: 浮動小数点数の実装により、非数字(NaN)値の2つのインスタンスはお互いに等価ではないことが宣言されます。

Eqが必要になる一例が、HashMap<K, V>のキーで、HashMap<K, V>が、2つのキーが同じであると判定できます。

順序付き比較のためのPartialOrdOrd

PartialOrdトレイトにより、ソートする目的で型のインスタンスを比較できます。PartialOrdを実装する型は、 <><=>=演算子を使用することができます。PartialEqも実装する型に対してのみ、 PartialOrdトレイトを適用できます。

PartialOrdを継承すると、partial_cmpメソッドを実装し、これは、与えられた値が順序付けられない時にNoneになるOption<Ordering>を返します。 その型のほとんどの値は比較できるものの、順序付けできない値の例として、非数字(NaN)浮動小数点値が挙げられます。 partial_cmpをあらゆる浮動小数点数とNaN浮動小数点数で呼び出すと、Noneが返るでしょう。

構造体に継承すると、フィールドが構造体定義で現れる順番で各フィールドの値を比較することで2つのインスタンスを比較します。 enumに継承すると、enum定義で先に定義された列挙子が、後に列挙された列挙子よりも小さいと考えられます。

PartialOrdトレイトが必要になる例には、低い値と高い値で指定される範囲の乱数を生成するrandクレートのgen_rangeメソッドが挙げられます。

Ordトレイトにより、注釈した型のあらゆる2つの値に対して、合法な順序付けが行えることがわかります。 Ordトレイトはcmpメソッドを実装し、これは、常に合法な順序付けが可能なので、Option<Ordering>ではなく、 Orderingを返します。PartialOrdEq(EqPartialEqも必要とします)も実装している型にしか、 Ordトレイトを適用することはできません。構造体とenumで継承したら、PartialOrdで、 partial_cmpの継承した実装と同じようにcmpは振る舞います。

Ordが必要になる例は、BTreeSet<T>に値を格納する時です。 これは、値のソート順に基づいてデータを格納するデータ構造です。

値を複製するCloneCopy

Cloneトレイトにより値のディープコピーを明示的に行うことができ、複製のプロセスは、任意のコードを実行し、 ヒープデータをコピーすることに関係がある可能性があります。Cloneについて詳しくは、 第4章の「変数とデータの相互作用法: Clone」節を参照されたし。

Cloneを継承すると、cloneメソッドを実装し、これは型全体に対して実装されると、 型の各部品に対してcloneを呼び出します。要するに、Cloneを継承するには、 型のフィールドと値全部もCloneを実装していなければならないということです。

Cloneが必要になる例は、スライスに対してto_vecメソッドを呼び出すことです。スライスは、 含んでいる型のインスタンスの所有権を持たないが、to_vecで返されるベクタはそのインスタンスを所有する必要があるので、 to_vecは各要素に対してcloneを呼び出します。故に、スライスに格納される型は、Cloneを実装しなければならないのです。

Copyトレイトにより、スタックに格納されたビットをコピーするだけで値を複製できます; 任意のコードは必要ありません。 Copyについて詳しくは、第4章の「スタックのみのデータ: Copy」を参照されたし。

Copyトレイトは、プログラマがメソッドをオーバーロードし、任意のコードが実行されないという前提を侵害することを妨げるメソッドは何も定義しません。 そのため、全プログラマは、値のコピーは非常に高速であることを前提にすることができます。

部品すべてがCopyを実装する任意の型に対してCopyを継承することができます。Cloneも実装する型に対してのみ、 Copyトレイトを適用することができます。何故なら、Copyを実装する型には、 Copyと同じ作業を行うClone瑣末(さまつ)な実装があるからです。

Copyトレイトは稀にしか必要になりません; Copyを実装する型では最適化が利用可能になります。 つまり、cloneを呼び出す必要がなくなり、コードがより簡潔になるということです。

Copyで可能なこと全てがCopyでも達成可能ですが、コードがより遅い可能性や、 cloneを使用しなければならない箇所があったりします。

値を固定サイズの値にマップするHash

Hashトレイトにより、任意のサイズの型のインスタンスを取り、そのインスタンスをハッシュ関数で固定サイズの値にマップできます。 Hashを継承すると、hashメソッドを実装します。hashの継承された実装は、 型の各部品に対して呼び出したhashの結果を組み合わせます。つまり、Hashを継承するには、 全フィールドと値もHashを実装しなければならないということです。

Hashが必要になる例は、HashMap<K, V>にキーを格納し、データを効率的に格納することです。

既定値のためのDefault

Defaultトレイトにより、型に対して既定値を生成できます。Defaultを継承すると、default関数を実装します。 default関数の継承された実装は、型の各部品に対してdefault関数を呼び出します。つまり、 Defaultを継承するには、型の全フィールドと値もDefaultを実装しなければならないということです。

Default::default関数は、 第5章の「構造体更新記法で他のインスタンスからインスタンスを生成する」節で議論した構造体更新記法と組み合わせてよく使用されます。 構造体のいくつかのフィールドをカスタマイズし、それから..Default::default()を使用して、 残りのフィールドに対して既定値をセットし使用することができます。

例えば、Defaultトレイトは、Option<T>インスタンスに対してメソッドunwrap_or_defaultを使用する時に必要になります。 Option<T>Noneならば、メソッドunwrap_or_defaultは、Option<T>に格納された型Tに対してDefault::defaultの結果を返します。