我有一些同时实现Hash
和MyTrait
的结构体,我把它们用作&dyn MyTrait
trait对象。
现在我想让&dyn MyTrait
也实现Hash
。我已经尝试了几件事:
- 简单地说,
trait MyTrait: Hash {}
:
the trait `MyTrait` cannot be made into an object
字符串
- 然后我试着这样做:
impl Hash for dyn 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) -> &dyn Any {
self as &dyn 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) -> &dyn Any;
}
impl<H: 'static + Hasher> AnyHasher for H {
fn as_any(&self) -> &dyn Any {
self as &dyn 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
for trait objects,这很难,因为需要trait对象的具体类型的信息。这是通过向下转换解决的,但我没有设法在这里应用该解决方案。
2条答案
按热度按时间irtuqstp1#
我不是RustMaven,但在我看来,你试图把Rust变成Java(不要生气:我真的很喜欢Java)。
如何创建可散列的trait对象?
你不想创建一个trait对象的哈希表(这很容易),你想创建一个trait对象的哈希表,这就是为什么你遇到困难。
问题
我总结如下:你有一些实现trait
MyTrait
,Hash
和Eq
的不同结构体,你想把这些混合结构体作为TunedMyTrait
trait对象放入一个哈希表中。这需要TunedMyTrait
是Hash
和Eq
的subtrait。但是MyTrait
可以成为trait对象,*TunedMyTrait
不能 *。我相信你知道为什么,但是我会尝试用this valuable resource来向其他读者解释清楚。(我用自己的话写的,如果你觉得不清楚,不要害羞,编辑它。)Trait对象依赖于一种叫做“对象安全”的东西(参见the RFC 255)。“对象安全”意味着:trait的所有方法都必须是对象安全的。
Rust大量使用堆栈,因此它必须知道它所能知道的所有东西的大小。在借用检查器之后,这是Rust的难点之一,也是Rust的优点之一。一个trait对象是有类型和大小的:它是某种“胖”指针,包含关于具体类型的信息。每个方法调用都委托给具体类型,使用
vtable
的方法。我不会详细介绍,但这种委托可能会发生一些问题,并且创建了“安全检查”来避免这些问题。这里:fn eq(&self, other: &Rhs) -> bool
其中Rhs = Self
不是对象安全的,因为在运行时,Rhs
被擦除,因此other
的具体类型和大小是未知的。fn hash<H: Hasher>(&self, hasher: &mut H)
不是对象安全的,因为vtable
不是为每个具体类型H
构建的。解决方案
好的。
MyTrait
是一个trait对象,但TunedMyTrait
不是。然而只有TunedMyTrait
对象可能是哈希表的有效键。你能做什么?您可以尝试,就像您所做的那样,破解对象安全机制。(有一个铸造尝试,How to test for equality between trait objects?),你现在有另一个黑客从@Boiethios如果你最终达到了你的目标,我可以想象代码的未来读者:“OMG,这家伙想做什么?”或者(更糟):“I'm not sure of what it does,but I'm pretty sure it would run faster if.
或者你也可以理性一点。有一些可能性:你使用一个哈希表,它的键实际上是相同的具体类型,你把你的
MyTrait
对象装箱,你使用一个枚举..可能还有其他的方法(正如我所说的,我不是RustMaven)。别误会我的意思破解一门语言真的很有趣,有助于深入理解它的机制和限制(注意:如果你没有问这个问题,我就不会仔细研究DST和trait对象,因此我感谢你)。但是如果你打算做一些严肃的事情,你必须严肃:Rust不是Java。
编辑
我想比较和散列运行时多态的对象。
这并不难,但你也想把它们放在
HashMap
中,这就是问题所在。我会给予你另一个见解。基本上,你知道哈希表是一个桶数组。Rust使用开放寻址来解决哈希冲突。(具体而言:罗宾汉散列),这意味着每个桶将包含0或1对
(key, value)
。当您将一对(key, value)
放入空桶时,元组(key, value)
写入缓冲区数组,在位置pair_start + index * sizeof::<K, V>()
,根据the definition ofoffset
。很明显,你需要 sized 对。如果你可以使用trait对象,你会有一个胖指针,它是有大小的。但这是不可能的,原因已经说过了。我提出的所有想法都集中在这一点上:有大小的键(假设值已经被大小化了)。具体类型:明显的大小。装箱:指针的大小。枚举:最大元素的大小+标签的大小+填充。
装箱基本示例
联系我们我努力在互联网上找到一个例子,但没有找到任何东西。所以我决定从头开始创建一个基本的装箱示例,但我不确定这是正确的方法。如果需要,请评论或编辑。*
首先,在trait中添加一个方法,该方法标识实现
MyTrait
的任何具体类型的每个示例,并具有comparable和hashable值,假设一个id
方法返回i64
:字符串
Foo
和Bar
具体类型将实现此方法(这里给出的实现完全是愚蠢的):型
现在,我们必须实现
Hash
和Eq
,以便将dyn MyTrait
放入HashMap
中。但是如果我们为dyn MyTrait
这样做,我们得到的trait不可能是trait对象,因为dyn MyTrait
没有大小。让我们为Box<dyn Trait>
实现它,它是大小:型
我们使用
id
方法来实现eq
和hash
。现在,想想
Box<dyn MyTrait>
:1.它的大小; 2.它实现了Hash
和Eq
。这意味着它可以用作HashMap
的密钥:型
输出量:
型
试试看:https://play.integer32.com/?gist=85edc6a92dd50bfacf2775c24359cd38&version=stable
我不确定它能解决你的问题,但我真的不知道你想做什么...
8dtrkrch2#
你可以把你想要的功能放在你的特质中,* 也就是说,* 你可以混合你的第二次和第三次尝试:
字符串
在我看来,以你的方式为trait实现
Hash
并不是一件好事,因为你不知道trait旁边的具体类型是什么。有人可以为同一类型实现trait,比如:型
在这种情况下,你想如何处理
Foo("hello")
和Bar("hello")
?它们是同一个项目吗?因为它们将具有相同的哈希值。在你的例子中,真实的问题是:* 你如何定义trait中的相同或不同?* 在我看来,处理这个问题的更好方法是从“业务”trait方法中计算hash,例如:
型
一个特质只是一个契约或一个事物的部分考虑(就像当你说一个“人”是一个“开发者”)。你不应该(在我的拙见中)把一个特质看作一个具体的类型。