我有一些实现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
的{{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,这很难,因为需要具体类型的特征对象。这是通过向下转换解决的,但我没有设法在这里应用该解决方案。
答案 0 :(得分:2)
我不是Rust专家,但在我看来,你试图将Rust变成Java(不要被冒犯:我真的喜欢Java)。
如何创建可散列的特征对象?
您不想创建特征对象的哈希表(这很容易),您想要创建一个不是特征对象的特征哈希表,并且&#39 ;为什么你遇到困难。
我总结一下:您有一些实现特征MyTrait
,Hash
和Eq
的各种结构,并且您希望将这些混合结构放入一个hashstable中作为{{{ 1}}特质对象。这需要
TunedMyTrait
是TunedMyTrait
和Hash
的子广告。但是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专家。)
修改强>
我想比较和散列运行时多态的对象。
这并不难,但你也想把它们放在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
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
:1。它的大小; 2.它实现了hash
和Box<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;)。你不应该(以我的拙见)将某种特质视为具体类型。