Rust:实现 Hash/Eq

Tes*_*est 1 rust

在结构上实现的最简单方法是什么Hash/Eq,以便具有相同属性的两个不同实例不相等?

考虑以下结构:

struct Person {
    name: String,
}
Run Code Online (Sandbox Code Playgroud)

Hash/Eq即使两个不同的人具有相同的名字,最简单的实现方法是什么?是某件事的唯一途径Box<>吗?

Sil*_*olo 9

你不知道。Rust 不是 Java。两个相同的Person实例在内存中由完全相同的位序列表示,因此无法区分。甚至Box不会在这里保存您:委托所包含值的实例EqBox。

按照您描述的方式比较指针相等性的唯一方法是使用std::ptr::eq, usingstd::pin确保指针不会更改。但是,我必须强调这一点,这是错误的做法。你不想要这种平等。这在 Rust 中没有意义。如果Person { name: "Joe" }和Person { name: "Joe" }是不同的对象,那么您的数据结构设计得很糟糕。您的工作就是添加一个区分字段。如果这些由数据库支持,您可以使用

struct Person {
  primary_key: u64,
  name: String,
}
Run Code Online (Sandbox Code Playgroud)

或者也许每个人都有一个十六进制的员工 ID。

struct Person {
  employee_id: String,
  name: String,
}
Run Code Online (Sandbox Code Playgroud)

关键是数据结构本身(Person在我们的示例中)对它的所有内容进行了编码。Rust 避开了 Java 式的概念,即每个对象本质上都具有与所有其他对象不同的身份,而有利于数据向世界明确地描述自身。


use*_*342 6

正如 @SilvioMayolo 所指出的,值“身份”在 Rust 中不是一个东西,因为 Rust 的值默认不是堆分配的。虽然您可以获取任何值的地址,但不能使用它来表示身份,每次将值移动到不同的变量、传递给函数或插入到容器中时,地址都会发生变化。您可以通过堆分配值来使地址稳定,但这需要在创建值时进行分配,并在每次访问时进行额外的取消引用。

对于堆分配其内容的值(例如Strings),您可以使用内容的地址来表示身份,如 @Smitop 的答案所示。但这也不是身份的良好表示,因为它会在字符串重新分配时随时发生变化,例如,如果您向其中附加一些数据。如果您从不打算增加琴弦,那么这个选项会很有效。否则,您必须使用其他东西。

一般来说,您可以显式地跟踪作为对象一部分的身份,而不是使用地址来表示身份。没有什么可以阻止您添加表示身份的字段,并在构造函数中分配它:

static NEXT_ID: AtomicU64 = AtomicU64::new(0);

pub struct Person {
    id: u64,
    name: String,
}

impl Person {
    /// Create a new unique person.
    pub fn new(name: String) -> Self {
        Person {
            id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
            name,
        }
    }

    /// Identity of a `Person`.
    ///
    /// Two persons with the same name will still have different identities.
    pub fn id(&self) -> u64 {
        self.id
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以实现Hash并Eq使用这个身份:

impl Hash for Person {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.id.hash(state);
    }
}

impl Eq for Person {}

impl PartialEq for Person {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

// We can't #[derive(Clone)] because that would clone the id, and we
// want to generate a new one instead.
impl Clone for Person {
    fn clone(&self) -> Person {
        Person::new(self.name.clone())
    }
}
Run Code Online (Sandbox Code Playgroud)

操场