簡體   English   中英

如何使用通用方法參數創建可散列的特征對象/特征對象?

[英]How can I create hashable trait objects / trait objects with generic method parameters?

我有一些實現HashMyTrait的結構。 我將它們用作&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)。

如何創建可散列的特征對象?

您不想創建特征對象的哈希表(這很容易),您想創建不是特征對象的特征哈希表,這就是您遇到困難的原因。

問題

我總結一下:您有一些實現特征MyTraitHashEq的各種結構,並且您希望將這些混合結構作為TunedMyTrait特征對象放入單個哈希表中。 這要求TunedMyTraitHashEq的子特征。 但是,雖然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具有可比較和可哈希值的任何具體類型的每個實例,比方說一個返回i64id方法:

trait MyTrait {
    fn id(&self) -> i64; // any comparable and hashable type works instead of i64
}

FooBar具體類型將實現這個方法(這里給出的實現是完全愚蠢的):

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
    }
}

現在,我們必須實現HashEq ,以便將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方法來實現eqhash

現在,想想Box<MyTrait> : 1. 它的大小; 2. 它實現了HashEq 這意味着它可以用作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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM