如何使用通用方法参数创建可哈希的特征对象/特征对象? [英] How can I create hashable trait objects / trait objects with generic method parameters?

查看:84
本文介绍了如何使用通用方法参数创建可哈希的特征对象/特征对象?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一些同时实现HashMyTrait的结构.我将它们用作&MyTrait特征对象.

I have some structs that implement both Hash and MyTrait. I'm using them as &MyTrait trait objects.

现在我希望&MyTrait也实现Hash.我已经尝试了几件事:

Now I want &MyTrait to also implement Hash. I've tried a few things:

  • 天真的trait MyTrait: Hash {}:

the trait `MyTrait` cannot be made into an object

  • 然后我尝试了这个:

  • Then I tried this:

    impl Hash for MyTrait {
        fn hash<H: Hasher>(&self, hasher: &mut H) {
            // ...
        }
    }
    

    但是我认为我需要委托给self具体类型的hash方法.

    but I need to delegate to the hash method of the concrete type of self, I think.

    因此,天真的下一步是将其放置在MyTrait上:

    So the naive next step is to put this on MyTrait:

    fn my_hash<H: Hasher>(&self, hasher: &mut H);
    

    这使我回到了第一点.

    我读了一些有关使用特征对象而不是通用参数的内容,这听起来很聪明,所以我将其放在了MyTrait

    I read something about using a trait object instead of generic parameter, which sounds smart, so I put this on MyTrait

    fn my_hash(&self, hasher: &mut H);
    

    然后我需要实际执行此操作.最好不要用手为每个特征:

    Then I need to actually implement this. Preferably not by hand for every trait:

    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 ...

    So I'd have to downcast Hasher

    如果要向下转换Hasher,我需要一个通用参数H,该参数可以转换为Any Hasher,让我们尝试:

    If downcasting Hasher is the way, I need a generic parameter H that can convert to an Any Hasher, Let's try:

    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)
        }
    }
    

    但是a

    the type parameter `H` is not constrained by the impl trait, self type, or predicates
    

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

    which I guess is true, but then I'm stuck. (Also it seems kind of ridiculous so far).

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

    Can this can be done? If so, how?

    我之前曾问过 PartialEq用于特征对象的情况,这很困难,因为需要信息来提供特征对象的具体类型.可以通过向下转换解决,但是我没有设法在此处应用该解决方案.

    I previously asked about PartialEq for trait objects, which was hard because information the concrete type of the trait object is needed. That was solved with downcasting, but I didn't manage to apply that solution here.

    推荐答案

    我不是Rust专家,但是在我看来,您试图将Rust变成Java(不要生气:我真的很喜欢Java ).

    I'm not a Rust expert, but it seems to me that you try to turn Rust into Java (don't be offended: I really do like Java).

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

    How can I create hashable trait objects?

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

    You don't want to create a hashtable of trait objects (it's easy), you want to create a hashtable of traits that are not trait objects and that's why you encounter difficulties.

    我总结:您有一些实现特征MyTraitHashEq的结构,并且您希望将这些混合结构作为TunedMyTrait特征对象放入一个哈希表中.这需要 TunedMyTraitHashEq的子特征.但是,虽然MyTrait可以成为特征对象,但 TunedMyTrait不能.

    I summarize: you have some various structs that implement traits MyTrait, Hash and Eq, and you would like to put those mixed structs into a single a hashstable as a TunedMyTrait trait objects. This requires TunedMyTrait to be a subtrait of Hash and Eq. But whereas MyTrait can be made a trait object, TunedMyTrait cannot.

    我确定您知道原因,但是我将尝试使用

    I'm sure you know why, but I will try to make it clear for other readers, using this valuable resource. (I put it in my own words, don't be shy and edit it if you think that isn't clear.) Trait objects rely on something that is called "object safety" (see the RFC 255). "Object safety" means: all methods of the trait must be object-safe.

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

    Rust makes an intensive usage of the stack, thus it has to know the size of everything it can. After the borrow checker, that's one of the difficulties and the beauties of Rust. A trait object is typed and sized: it is some kind of "fat" pointer that contains information on the concrete type. Every method call is delegated to the concrete type, using a vtable of methods. I don't get into details, but some issues may occur with this delegation and the "safety check" was created to avoid those issues. Here:

    • fn eq(&self, other: &Rhs) -> bool方法(其中Rhs = Self不是对象安全的),因为在运行时会擦除Rhs,因此other的具体类型和大小未知.
    • fn hash<H: Hasher>(&self, hasher: &mut H)方法不是对象安全的,因为不是为每种具体类型H构建的vtable.
    • the method fn eq(&self, other: &Rhs) -> bool where Rhs = Self is not object safe, because at runtime, Rhs was erased, and thus the concrete type and the size of other is not known.
    • the method fn hash<H: Hasher>(&self, hasher: &mut H) is not object safe, because the vtable is not built for every concrete type H.

    好的. MyTrait是特征对象,但TunedMyTrait不是.但是,只有TunedMyTrait对象可能是哈希表的有效键.你能做什么?

    Okay. MyTrait is a trait object, but TunedMyTrait is not. Yet only TunedMyTrait objects may be valid keys for your hashtable. What can you do?

    您可以像尝试那样尝试破解对象安全机制.您找到了破解PartialEq的解决方案(通过强制尝试如何来测试特征对象之间的相等性?如果您最终实现了目标,那么我可以想象该代码的未来读者:"OMG,这个人想做什么?"或(更糟糕):我不确定它的作用,但我敢肯定,如果...,它将运行得更快."您已经破解了该语言的保护,并且您的代码可能会产生比您要解决的问题更严重的问题.这让我想起了这样的讨论:在运行时获取类的通用类型.接着?您将如何处理这段代码?

    You can try, as you did, to hack the object safety mechanism. You found a solution to hack PartialEq (with a cast try, How to test for equality between trait objects?), and you have now another hack from @Boiethios (which basically makes of hash a non generic function). If you finally reach your goal, I can imagine the future reader of the code: "OMG, what is this guy trying to do?" or (worse): "I'm not sure of what it does, but I'm pretty sure it would run faster if...". You have hacked a protection of the language and your code is likely to create issues worse than the problem you are trying to solve. This reminds me this kind of discussion: Get generic type of class at runtime. And then? What will you do with this piece of code?

    或者您可以合理.有一些可能:您使用具有真正相同具体类型的键的哈希表,将MyTrait对象装箱,使用枚举...可能还有其他方式(如上所述,我不是Rust)专家).

    Or you can be reasonable. There are some possiblities: you use a hashtable with keys that are really of the same concrete type, you box your MyTrait objects, you use an enum... There may be other ways (as said, I'm not a Rust expert).

    不要误会我的意思:入侵一种语言真的很有趣,并且可以帮助您深刻理解其机制和局限性(注意:如果您没有问过这个问题,我就不会仔细研究DST和特征对象,因此,我谢谢你).但是,如果您打算做一些严肃的事情,则必须认真对待:Rust不是Java ...

    Don't get me wrong: hacking a language is really fun and helps to understand deeply its mechanics and limits (note: if you hadn't asked that question, I wouldn't have had a close look at DST and trait objects, thus I thank you). But you have to be serious if you intend to do something serious: Rust is not Java...

    编辑

    我想比较并散列运行时多态的对象.

    I want to compare and hash objects that are runtime-polymorphic.

    这并不困难,但是您也想将它们放在HashMap中,这就是问题所在.

    That's not difficult, but you also want to put them in a HashMap, and that is the problem.

    我将为您提供另外的见解.基本上,您知道哈希表是存储桶的数组. Rust使用开放式地址解决哈希冲突(特别是:Robin Hood哈希),这意味着每个存储桶将包含0或1对(key, value).当您放入一对在空的存储桶中,将元组(key, value)写在缓冲区数组中的位置

    I will give you another insight. Basically, you know that a hashtable is an array of buckets. Rust uses open adressing to resolve hash collisions (specifically: Robin Hood hashing), that means that every bucket will contain 0 or 1 pair (key, value). When you put a pair (key, value) in an empty bucket, the tuple (key, value) is written in the buffer array, at the position pair_start + index * sizeof::<K, V>(), according to the definition of offset. It's obvious that you need sized pairs.

    如果可以使用特征对象,则将具有大小合适的胖指针.但是由于已经说明的原因,这是不可能的.我提出的所有想法都集中在此:设置大小的键(假设值已经确定大小).混凝土类型:明显大小.装箱:指针的大小.枚举:最大元素的大小+标签的大小+填充.

    If you could use trait object, you would have fat pointer, which is sized. But that's not possible for the reasons already stated. All the ideas I proposed are focused on this: have sized keys (assuming that values are already sized). Concrete type: obviously sized. Boxing: size of a pointer. Enum: size of the biggest element + size of the tag + padding.

    警告:我尽力在Internet上找到一个示例,但没有找到任何东西.因此,我决定从头开始创建一个带有拳击的基本示例,但是我不确定这是正确的方法.请根据需要发表评论或进行编辑.

    首先,在您的特征中添加一种方法,该方法标识具有 comparable hashable 值(例如id)的实现MyTrait的任何具体类型的每个实例. >返回i64:

    First, add to your trait a method that identifies every instance of any concrete type that implements MyTrait with a comparable and hashable value, let's say an id method that returns an i64:

    trait MyTrait {
        fn id(&self) -> i64; // any comparable and hashable type works instead of i64
    }
    

    FooBar具体类型将实现此方法(此处给出的实现完全是愚蠢的):

    Foo and Bar concrete types will implement this method (the implementation given here is totally stupid):

    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>实现它,它的大小为:

    Now, we have to implement Hash and Eq, in order to put MyTrait in a HashMap. But if we do it for MyTrait, we get a trait that can't be a trait object, because MyTrait is not sized. Let's implement it for Box<Trait>, which is sized:

    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.

    We used the id method to implement eq and hash.

    现在,请考虑Box<MyTrait>:1.它的大小已确定; 2.它实现HashEq.这意味着它可以用作HashMap:

    Now, think of Box<MyTrait>: 1. it is sized; 2. it implements Hash and Eq. That means that it can be used as a key for a 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
    

    尝试一下:>://://play.integer32.com/?gist = 85edc6a92dd50bfacf2775c24359cd38& ; version = stable

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

    I'm not sure it solves your problem, but I don't really know what you are trying to do...

    这篇关于如何使用通用方法参数创建可哈希的特征对象/特征对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆