如何使用通用方法参数创建可哈希的特征对象/特征对象?

标记

我有一些同时实现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)。

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

您不想创建特征对象的哈希表(这很容易),您想要创建不是特征对象的特征的哈希表,这就是您遇到困难的原因。

问题

我总结:你必须实现某些特质各种结构MyTraitHash并且Eq,你想将这些混合结构成一个单一的一个hashstable为TunedMyTrait特征的对象。这必须TunedMyTraitHash和的子特性Eq但是虽然MyTrait可以成为特征对象,却TunedMyTrait不能

我确定您知道原因,但是我将尝试使用此宝贵资源,向其他读者明确说明(我用自己的话说,如果您认为不清楚,请不要害羞并对其进行编辑。)特性对象依赖于一种称为“对象安全性”的东西(请参阅RFC 255)。“对象安全”是指:特征的所有方法都必须是对象安全的。

Rust大量使用了堆栈,因此必须知道它可以容纳的所有内容的大小。在借阅检查器之后,这是Rust的困难和美丽之一。特征对象的类型和大小:它是某种“胖”指针,其中包含有关具体类型的信息。每个方法调用都使用vtableof方法委托给具体类型我不详细介绍,但是此委派可能会出现一些问题,因此创建了“安全检查”以避免这些问题。这里:

  • 所述方法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
}

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得到一个不能作为特征对象的特征,因为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

我不确定它是否可以解决您的问题,但我真的不知道您要做什么。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章