繁体   English   中英

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

[英]How can I create hashable trait objects / trait objects with generic method parameters?

我有一些实现HashMyTrait的结构。 我将它们用作&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) { // ... } }

    但我认为,我需要委托给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) -> &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是方式,我需要一个可以转换为Any Hasher的通用参数H ,让我们尝试:

     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

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

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

我之前问过关于 trait objects 的PartialEq ,这很难,因为需要 trait 对象的具体类型信息。 这是通过向下转换解决的,但我没有设法在此处应用该解决方案。

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

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

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

问题

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

我相信您知道原因,但我会尝试使用这个宝贵的资源让其他读者明白这一点。 (我用我自己的话来说,如果您认为不清楚,请不要害羞并对其进行编辑。)特征对象依赖于称为“对象安全”的东西(请参阅RFC 255 )。 “对象安全”意味着:特征的所有方法都必须是对象安全的。

Rust 大量使用堆栈,因此它必须知道它所能知道的一切的大小。 在借用检查器之后,这就是 Rust 的困难和优点之一。 特征对象是有类型和大小的:它是某种“胖”指针,包含有关具体类型的信息。 每个方法调用都委托给具体类型,使用方法的vtable 我没有详细说明,但是这个委托可能会出现一些问题,并且创建了“安全检查”来避免这些问题。 这里:

  • 方法fn eq(&self, other: &Rhs) -> bool where Rhs = Self不是对象安全的,因为在运行时, Rhs被擦除,因此other的具体类型和大小是未知的。
  • 方法fn hash<H: Hasher>(&self, hasher: &mut H)不是对象安全的,因为vtable不是为每个具体类型H构建的。

解决方案

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

您可以像以前一样尝试破解对象安全机制。 您找到了破解PartialEq的解决方案(通过强制转换尝试, 如何测试 trait 对象之间的相等性? ),并且您现在有了来自 @Boiethios 的另一个 hack(它基本上使 hash 成为非通用函数)。 如果你最终达到目标,我可以想象代码的未来读者:“天哪,这家伙想做什么?” 或者(更糟糕的):“我不确定它的作用,但我很确定如果......它会运行得更快”。 你已经破解了语言的保护,你的代码可能会产生比你试图解决的问题更糟糕的问题。 这让我想起了这样的讨论: Get generic type of class at runtime 接着? 你将如何处理这段代码?

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

不要误会我的意思:破解一门语言真的很有趣,有助于深入理解它的机制和限制(注意:如果你没有问过这个问题,我不会仔细研究 DST 和 trait 对象,因此我谢谢你)。 但是如果你打算做一些严肃的事情,你必须认真:Rust 不是 Java...

编辑

我想比较和散列运行时多态的对象。

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

我会给你另一个见解。 基本上,您知道哈希表是一个桶数组。 Rust 使用开放寻址来解决哈希冲突(特别是:罗宾汉哈希),这意味着每个桶将包含 0 或 1 对(key, value) (key, value) (key, value) pair_start + index * sizeof::<K, V>() _ offset的定义 很明显,您需要大小合适的配对。

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

拳击的基本例子

警告:我试图在互联网上找到一个例子,但没有找到任何东西。 所以我决定从头开始创建一个拳击的基本示例,但我不确定这是不是正确的方法。 如果需要,请发表评论或编辑。

首先,向您的 trait 添加一个方法,该方法标识实现MyTrait具有可比较和可哈希值的任何具体类型的每个实例,比方说一个返回i64id方法:

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这样做,我们会得到一个不能成为 trait 对象的 trait,因为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

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

您可以将所需的功能放入您的 trait 中,混合您的第二次和第三次尝试:

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

在我看来,以你的方式为 trait 实现Hash并不是一件好事,因为你不知道 trait 旁边的具体类型是什么。 有人可以为两种相同类型实现特征,例如:

struct Foo(String);
struct Bar(String);

在这种情况下,你想如何处理Foo("hello") vs Bar("hello") 它们是同一个项目吗? 因为它们将具有相同的哈希值。

就您而言,真正的问题是:您如何定义特征的相同或不同? 在我看来,更好的处理方法是从“业务”特征方法计算哈希,例如:

#[derive(Hash)]
struct Baz(...); // Business item
#[derive(Hash)]
struct Qux(...); // Another business item

trait MyTrait {
    // all those returned items make my MyTrait unique
    fn description(&self) -> &str;
    fn get_baz(&self) -> Baz;
    fn get_qux(&self) -> Qux;
}

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

特质只是对事物的约定或部分考虑(就像您说“人类”是“开发者”时一样)。 您不应该(以我的拙见)将特征视为具体类型。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM