との両方を実装する構造体がいくつかHash
ありMyTrait
ます。私はそれらを&MyTrait
トレイトオブジェクトとして使用しています。
今、私&MyTrait
も実装したいと思いますHash
。私はいくつかのことを試しました:
素朴に、trait MyTrait: Hash {}
:
the trait `MyTrait` cannot be made into an object
それから私はこれを試しました:
impl Hash for MyTrait {
fn hash<H: Hasher>(&self, hasher: &mut H) {
// ...
}
}
hash
でも具体的なタイプの方法に委任する必要があるとself
思います。
したがって、素朴な次のステップはこれを置くことMyTrait
です:
fn my_hash<H: Hasher>(&self, hasher: &mut H);
これで最初のポイントに戻ります。
ジェネリックパラメータの代わりにトレイトオブジェクトを使用することについて何かを読んだので、これを身に付けました MyTrait
fn my_hash(&self, hasher: &mut H);
次に、これを実際に実装する必要があります。できれば、すべての特性について手作業ではありません。
impl<T: 'static + Hash> MyTrait for T {
fn as_any(&self) -> &Any {
self as &Any
}
fn my_hash(&self, hasher: &mut Hasher) {
self.as_any().downcast_ref::<T>().unwrap().hash(hasher)
}
}
しかしその後
the trait bound `std::hash::Hasher: std::marker::Sized` is not satisfied
`std::hash::Hasher` does not have a constant size known at compile-time
だから私はダウンキャストする必要がありHasher
ます…
ダウンキャストHasher
が方法である場合は、にH
変換できる汎用パラメーターが必要です。Any
Hasher
試してみましょう。
trait AnyHasher {
fn as_any(&self) -> &Any;
}
impl<H: 'static + Hasher> AnyHasher for H {
fn as_any(&self) -> &Any {
self as &Any
}
}
そしてダウンキャストする
impl<T: 'static + Hash, H: 'static + Hasher> MyTrait for T {
// ...
fn my_hash(&self, hasher: &mut AnyHasher) {
let h = hasher.as_any().downcast_ref::<H>().unwrap();
self.as_any().downcast_ref::<T>().unwrap().hash(h)
}
}
しかし悲しいかな
the type parameter `H` is not constrained by the impl trait, self type, or predicates
私はそれが本当だと思います、しかしそれから私は立ち往生しています。(また、これまでのところ、ちょっとばかげているようです)。
これはできますか?もしそうなら、どのように?
以前PartialEq
、トレイトオブジェクトについて質問しましたが、トレイトオブジェクトの具体的なタイプに関する情報が必要なため、大変でした。それはダウンキャストで解決されましたが、私はここでその解決策を適用することができませんでした。
私はRustの専門家ではありませんが、RustをJavaに変えようとしているようです(気分を害しないでください。私は本当にJavaが好きです)。
ハッシュ可能なトレイトオブジェクトを作成するにはどうすればよいですか?
トレイトオブジェクトのハッシュテーブルを作成するのではなく(簡単です)、トレイトオブジェクトではないトレイトのハッシュテーブルを作成する必要があるため、問題が発生します。
私は要約:あなたは特性を実装するいくつかの様々な構造体を持っているMyTrait
、Hash
そしてEq
、あなたはのような単一のA hashstableにそれらの混合構造体を入れたいTunedMyTrait
形質オブジェクト。これはTunedMyTrait
、とのサブトレイトである必要がHash
ありEq
ます。しかしMyTrait
、特性オブジェクトにTunedMyTrait
することはできますが、できません。
理由はご存知だと思いますが、この貴重なリソースを使用して、他の読者にもわかりやすく説明します。(あなたはそれが明確ではないと思われる場合、私は私自身の言葉でそれを置く、それ恥ずかしがりや編集することはありません。)形質オブジェクトは、「オブジェクトの安全性」(参照と呼ばれる何かに依存しているRFC 255)。「オブジェクトの安全性」とは、特性のすべてのメソッドがオブジェクトに対して安全でなければならないことを意味します。
Rustはスタックを集中的に使用するため、可能な限りすべてのサイズを知る必要があります。借用チェッカーの後、それはRustの難しさと美しさの1つです。特性オブジェクトは型指定され、サイズが設定されます。これは、具体的な型に関する情報を含む、ある種の「太い」ポインタです。すべてのメソッド呼び出しは、vtable
ofメソッドを使用して具象型に委任されます。詳細については説明しませんが、この委任でいくつかの問題が発生する可能性があり、それらの問題を回避するために「安全性チェック」が作成されました。ここに:
fn eq(&self, other: &Rhs) -> bool
でRhs = Self
はないメソッドRhs
が消去されたため、具体的なタイプとサイズother
が不明です。fn hash<H: Hasher>(&self, hasher: &mut H)
てvtable
いるわけではないため、このメソッドはオブジェクトセーフではありませんH
。はい。MyTrait
は特性オブジェクトですが、そうでTunedMyTrait
はありません。ただしTunedMyTrait
、ハッシュテーブルの有効なキーはオブジェクトのみである可能性があります。あなたは何ができますか?
あなたがしたように、あなたはオブジェクトの安全メカニズムをハックすることを試みることができます。ハッキングの解決策を見つけPartialEq
(キャストトライ、特性オブジェクト間の同等性をテストする方法は?)、@ Boiethiosからの別のハックがあります(これは基本的にハッシュを非汎用関数にします)。最終的に目標を達成した場合、コードの将来の読者を想像することができます。「OMG、この男は何をしようとしているのですか?」または(さらに悪い):「それが何をするのかはわかりませんが、もし...ならもっと速く走ると確信しています。」言語の保護をハッキングしたため、コードによって、解決しようとしている問題よりも悪い問題が発生する可能性があります。これは私にこの種の議論を思い出させます:実行時にジェネリック型のクラスを取得します。その後?このコードで何をしますか?
またはあなたは合理的であることができます。いくつかの可能性があります:実際には同じ具象型のキーを持つハッシュテーブルを使用する、MyTrait
オブジェクトをボックス化する、列挙型を使用する...他の方法があるかもしれません(言ったように、私はRustの専門家ではありません)。
誤解しないでください。言語をハッキングすることは本当に楽しく、そのメカニズムと制限を深く理解するのに役立ちます(注:その質問をしていなかったら、DSTと特性オブジェクトを詳しく調べていなかったでしょう。ありがとうございます)。しかし、何か深刻なことをするつもりなら、あなたは真剣でなければなりません:RustはJavaではありません...
編集
ランタイム多型のオブジェクトを比較してハッシュしたいと思います。
それは難しいことではありませんが、あなたもそれらをに入れたいと思っていますHashMap
、そしてそれは問題です。
私はあなたに別の洞察を与えます。基本的に、ハッシュテーブルはバケットの配列であることを知っています。Rustはオープンアドレス指定を使用してハッシュの衝突(具体的にはRobin Hoodハッシュ)を解決します。つまり、すべてのバケットに0または1のペアが含まれます(key, value)
。空のバケットにペア(key, value)
を配置すると、の定義に従って、タプル(key, value)
がバッファ配列の位置pair_start + index * sizeof::<K, V>()
に書き込まれます。サイズのペアが必要なことは明らかです。offset
トレイトオブジェクトを使用できる場合は、サイズが変更されたファットポインタがあります。しかし、すでに述べた理由により、それは不可能です。私が提案したすべてのアイデアはこれに焦点を合わせています:サイズ設定されたキーを持っています(値がすでにサイズ設定されていると仮定します)。コンクリートタイプ:明らかにサイズ。ボクシング:ポインタのサイズ。列挙型:最大要素のサイズ+タグのサイズ+パディング。
警告:インターネットで例を見つけようと懸命に努力しましたが、何も見つかりませんでした。そこで、ボクシングの基本的な例を最初から作成することにしましたが、これが正しい方法かどうかはわかりません。必要に応じてコメントまたは編集してください。
まず、比較可能でハッシュ可能な値で実装MyTrait
する具象型のすべてのインスタンスを識別するメソッドをトレイトに追加します。たとえば、:を返すメソッドを追加します。id
i64
trait MyTrait {
fn id(&self) -> i64; // any comparable and hashable type works instead of i64
}
Foo
そしてBar
、具体的な種類は、(ここで与えられた実装は全く愚かである)、このメソッドを実装します。
struct Foo(u32);
impl MyTrait for Foo {
fn id(&self) -> i64 {
-(self.0 as i64)-1 // negative to avoid collisions with Bar
}
}
struct Bar(String);
impl MyTrait for Bar {
fn id(&self) -> i64 {
self.0.len() as i64 // positive to avoid collisions with Foo
}
}
今、私たちは、実装する必要Hash
とEq
置くために、MyTrait
でHashMap
。しかし、のためMyTrait
にそれを行うと、MyTrait
サイズが設定されていないため、トレイトオブジェクトにはなり得ないトレイトが得られます。Box<Trait>
サイズが次のように実装してみましょう。
impl Hash for Box<MyTrait> {
fn hash<H>(&self, state: &mut H) where H: Hasher {
self.id().hash(state)
}
}
impl PartialEq for Box<MyTrait> {
fn eq(&self, other: &Box<MyTrait>) -> bool {
self.id() == other.id()
}
}
impl Eq for Box<MyTrait> {}
このid
メソッドを使用してとを実装eq
しましたhash
。
さて、考えてみてくださいBox<MyTrait>
:1。サイズが決められています。2.実装Hash
しEq
ます。つまり、HashMap
:のキーとして使用できるということです。
fn main() {
let foo = Foo(42);
let bar = Bar("answer".into());
let mut my_map = HashMap::<Box<MyTrait>, i32>::new();
my_map.insert(Box::new(foo), 1);
my_map.insert(Box::new(bar), 2);
println!("{:?}", my_map.get(&(Box::new(Foo(42)) as Box<MyTrait>)));
println!("{:?}", my_map.get(&(Box::new(Foo(41)) as Box<MyTrait>)));
println!("{:?}", my_map.get(&(Box::new(Bar("answer".into())) as Box<MyTrait>)));
println!("{:?}", my_map.get(&(Box::new(Bar("question".into())) as Box<MyTrait>)));
}
出力:
Some(1)
None
Some(2)
None
試してみてください:https://play.integer32.com/?gist = 85edc6a92dd50bfacf2775c24359cd38&version = stable
それがあなたの問題を解決するかどうかはわかりませんが、あなたが何をしようとしているのか本当にわかりません...
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加