我有一些同时实现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
具体类型的方法self
。
因此,幼稚的下一步是将其放在MyTrait
:
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)
}
}
可惜
the type parameter `H` is not constrained by the impl trait, self type, or predicates
我猜是真的,但是后来我被困住了。(到目前为止,这似乎还是很荒谬的)。
能做到吗?如果是这样,怎么办?
之前,我曾询问过PartialEq
特质对象,这很困难,因为需要信息来提供特质对象的具体类型。这是通过向下转换解决的,但是我没有设法在此处应用该解决方案。
我不是Rust专家,但是在我看来,您尝试将Rust变成Java(不要冒犯:我真的很喜欢Java)。
如何创建可散列特征对象?
您不想创建特征对象的哈希表(这很容易),您想要创建不是特征对象的特征的哈希表,这就是您遇到困难的原因。
我总结:你必须实现某些特质各种结构MyTrait
,Hash
并且Eq
,你想将这些混合结构成一个单一的一个hashstable为TunedMyTrait
特征的对象。这必须TunedMyTrait
是Hash
和的子特性Eq
。但是虽然MyTrait
可以成为特征对象,却TunedMyTrait
不能。
我确定您知道原因,但是我将尝试使用此宝贵资源,向其他读者明确说明。(我用自己的话说,如果您认为不清楚,请不要害羞并对其进行编辑。)特性对象依赖于一种称为“对象安全性”的东西(请参阅RFC 255)。“对象安全”是指:特征的所有方法都必须是对象安全的。
Rust大量使用了堆栈,因此必须知道它可以容纳的所有内容的大小。在借阅检查器之后,这是Rust的困难和美丽之一。特征对象的类型和大小:它是某种“胖”指针,其中包含有关具体类型的信息。每个方法调用都使用vtable
of方法委托给具体类型。我不详细介绍,但是此委派可能会出现一些问题,因此创建了“安全检查”以避免这些问题。这里:
fn eq(&self, other: &Rhs) -> bool
,其中Rhs = Self
不对象安全的,因为在运行时,Rhs
被删除,因此,混凝土的类型和大小other
是不知道。fn hash<H: Hasher>(&self, hasher: &mut H)
不是对象安全的,因为vtable
并非针对每种具体类型都构建H
。好的。MyTrait
是特征对象,但TunedMyTrait
不是。但是,只有TunedMyTrait
对象可能是哈希表的有效键。你能做什么?
您可以像尝试那样尝试破解对象安全机制。您找到了一种解决黑客的方法PartialEq
(通过强制尝试,如何测试特征对象之间的相等性?),现在又有了@Boiethios的另一种黑客方法(它基本上使哈希成为非泛型函数)。如果您最终实现了目标,那么我可以想象该代码的未来读者:“ OMG,这个人想做什么?” 或(更糟):“我不确定它的作用,但我确信如果...,它将运行得更快。” 您已经破解了该语言的保护,并且您的代码可能会产生比您要解决的问题更严重的问题。这使我想起了这样的讨论:在运行时获取类的通用类型。然后?您将如何处理这段代码?
或者您可以合理。有一些可能性:您使用具有真正相同具体类型的键的哈希表,将MyTrait
对象装箱,使用枚举...可能还有其他方法(如上所述,我不是Rust专家)。
不要误会我的意思:入侵一种语言真的很有趣并且可以帮助您深刻理解其机制和局限性(注意:如果您没有问这个问题,那么我就不会仔细研究DST和trait对象,因此我谢谢你)。但是,如果您打算做一些严肃的事情,则必须认真对待:Rust不是Java ...
编辑
我想比较和散列运行时多态的对象。
这并不困难,但是您也想将它们放在中HashMap
,这就是问题所在。
我会给你另一个见解。基本上,您知道哈希表是存储桶的数组。Rust使用开放式地址解析哈希冲突(特别是:Robin Hood哈希),这意味着每个存储桶将包含0或1对(key, value)
。当将一对(key, value)
放入空桶中时,根据的定义,元组(key, value)
被写入缓冲区数组中的位置。很明显,您需要大小对。pair_start + index * sizeof::<K, V>()
offset
如果可以使用特征对象,则将具有大小合适的胖指针。但是由于已经说明的原因,这是不可能的。我提出的所有想法都集中在此:设置大小的键(假设值已经确定大小)。混凝土类型:明显尺寸。装箱:指针的大小。枚举:最大元素的大小+标签的大小+填充。
警告:我努力尝试在互联网上找到示例,但没有找到任何东西。因此,我决定从头开始创建一个带有拳击的基本示例,但是我不确定这是正确的方法。请根据需要评论或编辑。
首先,向您的特征添加一个方法,该方法标识MyTrait
以可比较且可哈希的值实现的任何具体类型的每个实例,假设该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
和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
我不确定它是否可以解决您的问题,但我真的不知道您要做什么。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句