すぐに使える特徴

標準の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) { ... } 

制限の少ないこの機能( ここでプレイできます

EqPartialEqよりもPartialEq 1つの制限であり、制限の点で「より小さい」という点でEqと変わらないという事実は、次の重要な結論を暗示しています。

Eqを実装するすべての構造は、 PartialEqも実装する必要がありますPartialEq

つまり、 PartialEqが構造で定義されていない場合、エントリ#[derive(Eq)]有能ではありません- エラーが PartialEqされます!

これを回避する最も簡単な方法:

 #[derive(Eq, PartialEq)] enum Foo { ... } 

前の例の関数fn foo<T: Eq>()を思い出してください。 そのセマンティクスにより、 T Eqとして定義されますが、 PartialEq構造を自動的に受け入れることがPartialEqます。

EqPartialEq 1つのプロパティは、少なくとも1つの要素がそれぞれEqPartialEq実装していない構造体に対して自動的に抽出できないことです。 例:

 #[derive(Eq, PartialEq)] struct Foo { bar: f32 } 

すでにわかっているように、 f32Eq実装していないため、エラーが発生します。

同様に、次のコードは機能しません。

 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を持つ構造はOrdPartialEq - 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と同じように機能するため、それらを組み合わせます:

HashDefault 、およびDebugは、すべてのメンバーがそれぞれHashDefaultおよびDebugサポートする構造に実装できます。

そして、これは必要なだけでなく、十分です。 余分な手間、特性、その他はありません。 ここでこの農場遊ぶことができます

結論Hashはすべてのパブリック構造に実装できます-不要ではありません。 DefaultDebug -あなたの好みに合わせて、しかしできれば。 これにより、コードを使用している人が構造を自分の構造に組み込むときに問題が発生しないようになります。 簡単に言えば、それが残念でない場合は、 #[derive(...)]と入力し、そこから賞金のすべてを注ぎます。


1と0


この記事の執筆時点(Rust 1.6 Stable)では、これらの特性は不安定であると宣言されています。 ただし、将来的には、加算( Add )および乗算( Mul )の操作と組み合わせて使用​​すると、任意のデータ構造のベクトル空間を定義できる可能性が高くなります。 これらの特性を実装するには、構造に次のプロパティが必要です。

For OneMul - x * T::one() == xと組み合わせて使用
ゼロの場合Add - x + T::zero() == xと組み合わせて使用​​しx + T::zero() == x

これらの特性は、構造体のすべてのメンバーがOneまたはZeroもサポートしてderive場合、 deriveを使用して宣言できます。

結論 :公共の構造体がいつかベクトル空間で使用される可能性を除外しない場合、たとえばあらゆる種類の代数に参加する場合、構造体にこれらの特性を実装することは理にかなっています。 しかし、これまでのところ、これらの特性について言及すると、コンパイラは誓うだけです...

最も結論的な警告


この特性を実装していないフィールドが、 deriveを通じていくつかのTraitを実装する構造体に入ると、コードはバラバラになります。 このために特性実装を削除すると、構造を使用している人の間でコードがばらばらになります。 したがって、 黄金律

特性が多いほど、責任は大きくなります。

Source: https://habr.com/ru/post/J277461/


All Articles