標準のRust言語ライブラリには、
派生を使用して無料で宣言できるいくつかの特性があり
ます 。 これらの特性は、独自の構造を宣言するときに役立ちます。さまざまなオープンソースライブラリでよく見られますが、その実装はコンパイラによって生成され、疑問を引き起こす可能性があります。
よく見る:
#[derive(RustcEncodable, RustcDecodable, Clone, Eq, Default)] struct Foo { }
それが何でどこがわからないのですか
コンパイラー自体は、
#[derive]を使用して、いくつかの特性の単純な「組み込み」実装を提供できます。 もちろん、より複雑な動作が必要な場合は、これらの同じ特性を手動で実装することもできます。
そのため、「抽出」できる特性のサンプルリストを以下に示します(派生することでそのように変換されます)。
さらに、不安定:
- [↓] ゼロ 、 ワン -「数値」構造のユニティとゼロの値を設定します
上記の特性はすべて、
std標準ライブラリにあります。 さらに、自分の手で作成されたライブラリを見つけることができます。
#[derive]を使用して抽出された特性もあります。 例は、一般的に使用される
RustcEncodable / RustcDecodableです。 非常にトリッキーなマクロを使用して、形質の
deriveサポートを実装できます(これは、ほとんどの場合、半分のケースでは機能しません:))が、これは私たちの記事の範囲外です。
上記の特性を考慮してください
特性を比較する
それらを使用すると、構造に
等価関係のモデルを配置できます。 その後、構造を比較、整理、構造化、整理できます。 それらをより詳細に検討しましょう。
EqおよびPartialEq
foo == barかどうかについては、2つの類似した特性が原因です。 しかし、なぜこのような単純な2つの異なる特性があるのでしょうか? そして、解決策はこれです:
PartialEqはそれ自体との同等性を保証しません:a == aという事実ではありません
これはどのようにできますか? たとえば、数値NaN!= NaN
use std::f32; fn main() { let a = f32::NAN; let b = f32::NAN; println!("{}", a == b); }
falseを返しますある種の比較機能を備えたライブラリの作成者を想像してください。
fn foobar<T>(param: T) { ... }
T制限を選択する方法は? 完全な等価性が基本であり、それがなければ関数がばらばらになる場合、次のように記述します。
fn foo<T: Eq>(param: T) { ... }
ただし、この場合、たとえば、この関数に
f32を渡すことはできません。前述したように、
PartialEqのみをサポートしてい
PartialEq 。
そのような制限が重要ではなく、関数がパラメーターに対して安定している場合、セマンティクスは次のようになります。
fn bar<T: PartialEq>(param: T) { ... }
制限の少ないこの機能(
ここでプレイでき
ます )
Eqは
PartialEqよりも
PartialEq 1つの制限であり、制限の点で「より小さい」という点で
Eqと変わらないという事実は、次の重要な結論を暗示しています。
Eqを実装するすべての構造は、 PartialEqも実装する必要がありますPartialEq
つまり、
PartialEqが構造で定義されていない場合、エントリ
#[derive(Eq)]有能ではありません-
エラーが PartialEqされ
ます!これを回避する最も簡単な方法:
#[derive(Eq, PartialEq)] enum Foo { ... }
前の例の関数
fn foo<T: Eq>()を思い出してください。 そのセマンティクスにより、
T Eqとして定義されますが、
PartialEq構造を自動的に受け入れることが
PartialEqます。
Eqと
PartialEq 1つのプロパティは、少なくとも1つの要素がそれぞれ
Eqと
PartialEq実装していない構造体に対して自動的に抽出できないことです。 例:
#[derive(Eq, PartialEq)] struct Foo { bar: f32 }
すでにわかっているように、
f32は
Eq実装していないため、エラーが発生します。
同様に、次のコードは機能しません。
struct Foo {} #[derive(PartialEq)] struct Bar { foo: Foo }
Foo構造は
PartialEq実装していないため(
ここでプレイでき
ます )
ただし、
この動作はすべての派生derivableな特性に対応します 。 これはさらに見る
結論:コードを書くときは、
Eq (可能な場合)を実装するか、少なくともすべてのパブリック構造体に少なくとも
PartialEqを実装するのが賢明です。 そして、いくつかのオブジェクトを比較することを決めたとき、突然のコンパイルエラーからコードを使用している人を救うか、自分の構造を自分の構造に含めて、タンバリンなしで比較を実装することができます!
OrdおよびPartialOrd
繰り返しますが、2つの同様の特性がありますが、今回のみがより少ない責任を負います。 ただし、それらは
Eqおよび
PartialEqよりも強く異なります。
PartialOrd PartialOrdは、比較関数を追加します: > 、 < 、 > = 、 <= PartialOrd = 、 =そして、厳密な順序付けを実装します: Option <Ordering>
use std::cmp::Ordering; let result = 1.0.partial_cmp(&2.0); assert_eq!(result, Some(Ordering::Less)); let result = 1.0.partial_cmp(&1.0); assert_eq!(result, Some(Ordering::Equal)); let result = 2.0.partial_cmp(&1.0); assert_eq!(result, Some(Ordering::Greater)); let result = std::f64::NAN.partial_cmp(&1.0); assert_eq!(result, None);
特性Ordは、「厳密な」順序付けを実装します。
use std::cmp::Ordering; assert_eq!(5.cmp(&10), Ordering::Less); assert_eq!(10.cmp(&5), Ordering::Greater); assert_eq!(5.cmp(&5), Ordering::Equal);
これらの特性の依存関係を見ると、すべてが明らかになります。
Ordを実装するには、フレームワークが Eq実装する必要があります。
PartialOrdを実装PartialOrdには、構造体にPartialOrdを実装する必要があります
確かに、構造がそれ自体に等しくないことが判明した場合、「より大きい」または「より大きいまたは等しい」値が2番目の値に相対的であるかどうかを確実に言うことは可能ですか。
また、
Ordでは
PartialOrdの実装が
PartialOrdであるため、このような構造のすべての比較演算は保証された状態で機能します。 一般的に、さらに
PartialEqおよび
PartialEqとの完全なアナロジー。
ここでこの経済のすべてで遊ぶことができ
ます 。
結論 :
Eqおよび
PartialEqとの類推により、
Ordまたは少なくとも
PartialOrdを、これが可能かつ論理的なすべての構造に実装しようとします。 したがって、
Eqを持つ構造は
Ord 、
PartialEq -
PartialOrd持つ構造をサポートできます。
クローンとコピー
ロシア語でさえ、「クローニング」と「コピー」という言葉は区別がつかない同義語です。 しかし、Rustでは、これらは2つの異なるものです。 今、私たちはそれらに対処します
Clone使用すると、コピーを使用してTからTを作成できます。
Copyは、割り当て時に変数の値を「 移動 」するのではなく、 Copyすることができます。
要するに、これらの特性は一般にさまざまなことに責任があり、それらは次の事実によってのみ関連しています:
Copyを実装するには、構造にClone実装する必要があります。
Cloneは基本的に一般的なものです。 想定されるほとんどすべてのものに実装されています。 そして、生成されたオブジェクトのコピーを
clone()関数から返すことでオブジェクトをコピーすることは、そのようなオブジェクトがメモリに保存されなかったほど大きな問題ではありません-新しいオブジェクトを作成し、同じフォームに持って帰ります。
Copyは別の歌です。 基本的に、ここでの「コピー」とは、オブジェクトを1バイトずつ転送することです。 また、すべての構造がこの機能をサポートしているわけではありません。 たとえば、ポインターの「ダム」コピーでは、同じ場所を指す2つのポインターが作成されますが、これは「安全な」Rustイデオロギーと完全に矛盾しています。 または、たとえば、ある種のメタデータを格納するオブジェクトも、バイト単位でコピーできません。
ただし、
Copyすると、転送のセマンティクスを簡単に処理できます。
#[derive(Debug, Copy, Clone)] struct Foo; #[derive(Debug, Clone)] struct Bar; fn main(){ let foo = Foo; let bar = foo; println!("foo is {:?} and bar is {:?}", foo, bar); let foo = Bar; let bar = foo; println!("foo is {:?} and bar is {:?}", foo, bar); }
最初の
println! 動作します、2番目-いいえ。 そして、違いは
Copyです。
ここで遊ぶことができ
ます 。
結論 :公式ドックでアドバイスされているように、可能な限り一般的に
Copyを実装します。 ただし、一般的な場合、
Dropが実装されている場合は実装できません。 一般的なケースでは、構造体のデストラクタを呼び出す必要がある場合、単純なメモリキャストではコピーできないようにデストラクタが作成されるためです。
Cloneはどこにも傷をつけないようです。
ハッシュ、デフォルト、およびデバッグ
特性
Hashは、構造からハッシュを取得する可能性を担当します。 これは、このような構造
HashMapまたは
HashSetを構成するために必要な条件です。
Default特性は、新しく作成された構造の初期(デフォルト)値を担当します。 多くの場合、
Defaultを実装する構造がさまざまなデータライブラリに必要です。 この特性の実装は、システムのパラメーターを特徴付ける構造にも役立ちます-毎回書き込むのが面倒なデフォルトのパラメーターが常にあります:)
Debugトレイトは、構造をテキスト形式の文字列として表示します。 ロギングライブラリを除き、どこにも必要ありませんが、独自のプログラムをデバッグするときに役立ちます。
特性はそれ自体が異なり、それらは
deriveと同じように機能するため、それらを組み合わせます:
Hash 、 Default 、およびDebugは、すべてのメンバーがそれぞれHash 、 DefaultおよびDebugサポートする構造に実装できます。
そして、これは必要なだけでなく、十分です。 余分な手間、特性、その他はありません。
ここでこの農場
で遊ぶことができ
ます 。
結論 :
Hashはすべてのパブリック構造に実装できます-不要ではありません。
Defaultと
Debug -あなたの好みに合わせて、しかしできれば。 これにより、コードを使用している人が構造を自分の構造に組み込むときに問題が発生しないようになります。 簡単に言えば、それが残念でない場合は、
#[derive(...)]と入力し、そこから賞金のすべてを注ぎます。
1と0
この記事の執筆時点(Rust 1.6 Stable)では、これらの特性は不安定であると宣言されています。 ただし、将来的には、加算(
Add )および乗算(
Mul )の操作と組み合わせて使用すると、任意のデータ構造のベクトル空間を定義できる可能性が高くなります。 これらの特性を実装するには、構造に次のプロパティが必要です。
For One : Mul - x * T::one() == xと組み合わせて使用
ゼロの場合 : Add - x + T::zero() == xと組み合わせて使用しx + T::zero() == x
これらの特性は、構造体のすべてのメンバーが
Oneまたは
Zeroもサポートして
derive場合、
deriveを使用して宣言できます。
結論 :公共の構造体がいつかベクトル空間で使用される可能性を除外しない場合、たとえばあらゆる種類の代数に参加する場合、構造体にこれらの特性を実装することは理にかなっています。 しかし、これまでのところ、これらの特性について言及すると、コンパイラ
は誓うだけです...
最も結論的な警告
この特性を実装していないフィールドが、
deriveを通じていくつかの
Traitを実装する構造体に入ると、コードはバラバラになります。 このために特性
の実装を
削除すると、構造を使用している人の間でコードがばらばらになります。 したがって、
黄金律 :
特性が多いほど、責任は大きくなります。