[英]How can I create hashable trait objects / trait objects with generic method parameters?
我有一些實現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) { // ... } }
但我認為,我需要委托給self
的具體類型的hash
方法。
所以天真的下一步就是把它放在MyTrait
上:
fn my_hash<H: Hasher>(&self, hasher: &mut H);
這讓我回到了第一點。
我讀了一些關於使用 trait 對象而不是泛型參數的內容,這聽起來很聰明,所以我把它放在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
是方式,我需要一個可以轉換為Any
Hasher
的通用參數H
,讓我們嘗試:
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
我猜這是真的,但后來我被困住了。 (到目前為止,這似乎有點荒謬)。
這可以做到嗎? 如果是這樣,怎么做?
我之前問過關於 trait objects 的PartialEq
,這很難,因為需要 trait 對象的具體類型信息。 這是通過向下轉換解決的,但我沒有設法在此處應用該解決方案。
我不是 Rust 專家,但在我看來你試圖將 Rust 變成 Java(不要生氣:我真的很喜歡 Java)。
如何創建可散列的特征對象?
您不想創建特征對象的哈希表(這很容易),您想創建不是特征對象的特征哈希表,這就是您遇到困難的原因。
我總結一下:您有一些實現特征MyTrait
、 Hash
和Eq
的各種結構,並且您希望將這些混合結構作為TunedMyTrait
特征對象放入單個哈希表中。 這要求TunedMyTrait
是Hash
和Eq
的子特征。 但是,雖然MyTrait
可以成為特征對象, TunedMyTrait
不能。
我相信您知道原因,但我會嘗試使用這個寶貴的資源讓其他讀者明白這一點。 (我用我自己的話來說,如果您認為不清楚,請不要害羞並對其進行編輯。)特征對象依賴於稱為“對象安全”的東西(請參閱RFC 255 )。 “對象安全”意味着:特征的所有方法都必須是對象安全的。
Rust 大量使用堆棧,因此它必須知道它所能知道的一切的大小。 在借用檢查器之后,這就是 Rust 的困難和優點之一。 特征對象是有類型和大小的:它是某種“胖”指針,包含有關具體類型的信息。 每個方法調用都委托給具體類型,使用方法的vtable
。 我沒有詳細說明,但是這個委托可能會出現一些問題,並且創建了“安全檢查”來避免這些問題。 這里:
fn eq(&self, other: &Rhs) -> bool
where Rhs = Self
不是對象安全的,因為在運行時, Rhs
被擦除,因此other
的具體類型和大小是未知的。fn hash<H: Hasher>(&self, hasher: &mut H)
不是對象安全的,因為vtable
不是為每個具體類型H
構建的。好的。 MyTrait
是一個特征對象,但TunedMyTrait
不是。 然而,只有TunedMyTrait
對象可能是哈希表的有效鍵。 你能做什么?
您可以像以前一樣嘗試破解對象安全機制。 您找到了破解PartialEq
的解決方案(通過強制轉換嘗試, 如何測試 trait 對象之間的相等性? ),並且您現在有了來自 @Boiethios 的另一個 hack(它基本上使 hash 成為非通用函數)。 如果你最終達到目標,我可以想象代碼的未來讀者:“天哪,這家伙想做什么?” 或者(更糟糕的):“我不確定它的作用,但我很確定如果......它會運行得更快”。 你已經破解了語言的保護,你的代碼可能會產生比你試圖解決的問題更糟糕的問題。 這讓我想起了這樣的討論: Get generic type of class at runtime 。 接着? 你將如何處理這段代碼?
或者你可以講道理。 有一些可能性:您使用具有相同具體類型的鍵的哈希表,將MyTrait
對象裝箱,使用枚舉...可能還有其他方法(如前所述,我不是 Rust 專家) .
不要誤會我的意思:破解一門語言真的很有趣,有助於深入理解它的機制和限制(注意:如果你沒有問過這個問題,我不會仔細研究 DST 和 trait 對象,因此我謝謝你)。 但是如果你打算做一些嚴肅的事情,你必須認真:Rust 不是 Java...
編輯
我想比較和散列運行時多態的對象。
這並不難,但您還想將它們放在HashMap
中,這就是問題所在。
我會給你另一個見解。 基本上,您知道哈希表是一個桶數組。 Rust 使用開放尋址來解決哈希沖突(特別是:羅賓漢哈希),這意味着每個桶將包含 0 或 1 對(key, value)
。 (key, value)
(key, value)
pair_start + index * sizeof::<K, V>()
_ offset
的定義。 很明顯,您需要大小合適的配對。
如果您可以使用 trait 對象,您將擁有大小合適的胖指針。 但由於已經說明的原因,這是不可能的。 我提出的所有想法都集中在這一點上:有大小的鍵(假設值已經確定了大小)。 混凝土類型:明顯尺寸。 裝箱:指針的大小。 枚舉:最大元素的大小+標簽的大小+填充。
警告:我試圖在互聯網上找到一個例子,但沒有找到任何東西。 所以我決定從頭開始創建一個拳擊的基本示例,但我不確定這是不是正確的方法。 如果需要,請發表評論或編輯。
首先,向您的 trait 添加一個方法,該方法標識實現MyTrait
具有可比較和可哈希值的任何具體類型的每個實例,比方說一個返回i64
的id
方法:
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
這樣做,我們會得到一個不能成為 trait 對象的 trait,因為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
我不確定它是否能解決您的問題,但我真的不知道您要做什么...
您可以將所需的功能放入您的 trait 中,即混合您的第二次和第三次嘗試:
use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;
use std::collections::HashSet;
#[derive(Hash)]
struct Foo(i32);
#[derive(Hash)]
struct Bar(String);
// Put the desired functionalities in your trait
trait MyTrait {
fn my_hash(&self, h: &mut Hasher);
fn my_eq(&self, other: &MyTrait) -> bool {
let mut hasher1 = DefaultHasher::new();
let mut hasher2 = DefaultHasher::new();
self.my_hash(&mut hasher1);
other.my_hash(&mut hasher2);
hasher1.finish() == hasher2.finish()
}
// other funcs
}
impl MyTrait for Foo {
fn my_hash(&self, mut h: &mut Hasher) {
self.hash(&mut h)
}
}
impl MyTrait for Bar {
fn my_hash(&self, mut h: &mut Hasher) {
self.hash(&mut h)
}
}
// Implement needed traits for your trait
impl Hash for MyTrait {
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.my_hash(hasher)
}
}
impl PartialEq for MyTrait {
fn eq(&self, other: &MyTrait) -> bool {
self.my_eq(other)
}
}
impl Eq for MyTrait {}
// This compiles
fn main() {
let foo = Foo(42);
let bar = Bar("answer".into());
let mut set = HashSet::new();
set.insert(&foo as &MyTrait);
set.insert(&bar);
}
在我看來,以你的方式為 trait 實現Hash
並不是一件好事,因為你不知道 trait 旁邊的具體類型是什么。 有人可以為兩種相同類型實現特征,例如:
struct Foo(String);
struct Bar(String);
在這種情況下,你想如何處理Foo("hello")
vs Bar("hello")
? 它們是同一個項目嗎? 因為它們將具有相同的哈希值。
就您而言,真正的問題是:您如何定義特征的相同或不同? 在我看來,更好的處理方法是從“業務”特征方法計算哈希,例如:
#[derive(Hash)]
struct Baz(...); // Business item
#[derive(Hash)]
struct Qux(...); // Another business item
trait MyTrait {
// all those returned items make my MyTrait unique
fn description(&self) -> &str;
fn get_baz(&self) -> Baz;
fn get_qux(&self) -> Qux;
}
impl Hash for MyTrait {
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.description().hash(hasher);
self.get_baz().hash(hasher);
self.get_qux().hash(hasher);
}
}
特質只是對事物的約定或部分考慮(就像您說“人類”是“開發者”時一樣)。 您不應該(以我的拙見)將特征視為具體類型。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.