如何使用通用方法参数创建可散列特征对象/特征对象?

时间:2018-04-07 20:03:13

标签: generics rust traits

我有一些实现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) {
            // ...
        }
    }
    

    但我认为我需要委托具体类型hash的{​​{1}}方法。

  • 所以天真的下一步就是把它放在self

    MyTrait

    让我回到第一点。

  • 我读过一些关于使用trait对象而不是泛型参数的内容,这听起来很聪明,所以我把它放在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)
        }
    }
    

    我猜这是真的,但后来我被困住了。 (到目前为止,这似乎有点荒谬。)

这可以做到吗?如果是这样,怎么样?

我之前询问过PartialEq for trait objects,这很难,因为需要具体类型的特征对象。这是通过向下转换解决的,但我没有设法在这里应用该解决方案。

2 个答案:

答案 0 :(得分:2)

我不是Rust专家,但在我看来,你试图将Rust变成Java(不要被冒犯:我真的喜欢Java)。

  

如何创建可散列的特征对象?

您不想创建特征对象的哈希表(这很容易),您想要创建一个不是特征对象的特征哈希表,并且&#39 ;为什么你遇到困难。

问题

我总结一下:您有一些实现特征MyTraitHashEq的各种结构,并且您希望将这些混合结构放入一个hashstable中作为{{{ 1}}特质对象。这需要 TunedMyTraitTunedMyTraitHash的子广告。但是Eq可以成为特质对象, MyTrait不能

我确定你知道原因,但我会尝试使用this valuable resource向其他读者说清楚。 (我用我自己的话说,如果你认为不清楚,不要害羞并编辑它。)特质对象依赖于所谓的对象安全&#34; (见the RFC 255)。 &#34;对象安全&#34;意味着:特征的所有方法都必须是对象安全的。

Rust会对堆栈进行大量使用,因此必须知道所有内容的大小。在借用检查员之后,这是Rust的困难之一和美女之一。特征对象的类型和大小:它是某种&#34;脂肪&#34;包含具体类型信息的指针。每个方法调用都使用TunedMyTrait方法委托给具体类型。我没有详细说明,但是这个代表团和安全检查可能会出现一些问题&#34;是为避免这些问题而创建的。这里:

  • 方法vtable,其中fn eq(&self, other: &Rhs) -> bool不是对象安全的,因为在运行时,Rhs = Self被删除,因此Rhs的具体类型和大小未知
  • 方法other不是对象安全的,因为fn hash<H: Hasher>(&self, hasher: &mut H)不是为每个具体类型vtable构建的。

解决方案

好。 H是一个特质对象,但MyTrait不是。但是只有TunedMyTrait个对象可能是哈希表的有效密钥。你能做什么?

你可以像你一样尝试破解对象安全机制。你找到了一个hack TunedMyTrait的解决方案(使用强制转换,How to test for equality between trait objects?),你现在又有了来自@Boiethios的黑客攻击(它基本上使得hash成为非泛型函数)。如果你最终达到了你的目标,我可以想象代码的未来读者:&#34; OMG,这个人试图做什么?&#34;或者(更糟糕的):&#34;我不确定它的作用,但我很确定如果...&#34;它会跑得更快。您已经破解了对语言的保护,您的代码可能会产生比您尝试解决的问题更糟糕的问题。这让我想起了这种讨论:Get generic type of class at runtime。然后?您将如何处理这段代码?

或者你可以合理。有一些可能性:你使用一个哈希表,其密钥实际上是相同的具体类型,你用PartialEq个对象包装,你使用一个枚举......可能有其他方式(如上所述,我&#39;我不是Rust专家。)

不要误会我的意思:黑客攻击一种语言真的很有趣并且有助于深入理解它的机制和限制(注意:如果你没有问过这个问题,我就不会有仔细看看DST和特质对象,我感谢你)。但是如果你打算做一些严肃的事情,你必须认真对待:Rust不是Java ......

修改

  

我想比较和散列运行时多态的对象。

这并不难,但你也想把它们放在MyTrait中,这就是问题所在。

我会给你另一个见解。基本上,您知道哈希表是一个桶阵列。 Rust使用开放地址解决散列冲突(特别是:Robin Hood散列),这意味着每个存储桶将包含0或1对HashMap。当您put a pair (key, value) in an empty bucket时,根据pair_start + index * sizeof::<K, V>(),元组(key, value)将写入缓冲区数组中的the definition of offset位置。很明显,你需要大小的对。

如果你可以使用trait对象,你会有胖指针,它的大小。但由于已经陈述的原因,这是不可能的。我提出的所有想法都集中于此:使用大小的键(假设值已经调整大小)。混凝土类型:尺寸明显。拳击:指针的大小。枚举:最大元素的大小+标记+填充的大小。

装箱的基本示例

警告:我努力在互联网上找到一个例子,但没有找到任何东西。所以我决定从头开始创建一个拳击的基本例子,但我不确定这是正确的方法。如果需要,请评论或编辑。

首先,在您的特征中添加一个方法,该方法使用可比较可哈希值来标识实现(key, value)的任何具体类型的每个实例,让&#39 ; s表示返回MyTrait的{​​{1}}方法:

id

i64trait MyTrait { fn id(&self) -> i64; // any comparable and hashable type works instead of i64 } 具体类型将实现此方法(此处给出的实现完全是愚蠢的):

Foo

现在,我们必须实施Barstruct 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:1。它的大小; 2.它实现了hashBox<MyTrait>。这意味着它可以用作Hash

的键
Eq

}

输出:

HashMap

试一试:https://play.integer32.com/?gist=85edc6a92dd50bfacf2775c24359cd38&version=stable

我不确定它能解决你的问题,但我真的不知道你要做什么......

答案 1 :(得分:1)

你可以在你的特性中加入所需的功能,你混合你的第二次和第三次尝试:

std::map<int, std::vector<int>> my_map;
my_map[4]; // default-constructs a vector there

在我看来,以你的方式实现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); } 特征并不是一件好事,因为你不知道特质旁边的具体类型是什么。有人可以为同一类型实现特征,例如:

Hash

在这种情况下,您希望如何处理struct Foo(String); struct Bar(String); vs Foo("hello")?它们是同一个项目吗?因为它们将具有相同的哈希值。

在您的情况下,真正的问题是:如何定义特征中的相同或不同?在我看来,更好的方法是从&计算哈希值#34;业务&#34;特征方法,例如:

Bar("hello")

特质只是一种契约或对事物的部分考虑(就像你说'#34;人是&#34;是&#34;开发者&#34;)。你不应该(以我的拙见)将某种特质视为具体类型。