拥有的字符串和借用的字符串是否保证哈希值相同?

5

Stringstr 都实现了 Hash 接口,因此我们可以对它们进行哈希。看起来,目前所有的拥有和借用字符串都哈希到了相同的值,因此这个断言是成立的:

use std::hash::Hash;
use std::hash::Hasher;
use std::collections::hash_map::DefaultHasher;
pub fn main() {
    let hash1 = {
        let x: String = "abc".to_owned();
        let mut hasher = DefaultHasher::new();
        x.hash(&mut hasher);
        hasher.finish()
    };
    let hash2 = {
        let x: &str = "abc";
        let mut hasher = DefaultHasher::new();
        x.hash(&mut hasher);
        hasher.finish()
    };
    assert!(hash1 == hash2);
}

我正在编写代码,利用 HashMapraw_entry API 中的这种行为。具体来说,我正在使用一个 HashMap,其中键是枚举类型,但为了减少重复分配内存,我想要使用这些枚举类型的“borrowed”版本进行查找。
换句话说,在下面的代码中,我需要确保两个断言都成功,而不管使用哪个实现的 Hasher。 我觉得这取决于 StringstrHash 实现提供的保证。
use std::hash::Hash;
use std::hash::Hasher;
use std::collections::hash_map::DefaultHasher;
pub fn main() {
    {
        #[derive(Hash)]
        enum E1 {
            First(i32),
            Second(String),
        }
        #[derive(Hash)]
        enum E2<'a> {
            First(i32),
            Second(&'a str),
        }
        let hash1 = {
            let x: E1 = E1::First(100);
            let mut hasher = DefaultHasher::new();
            x.hash(&mut hasher);
            hasher.finish()
        };
        let hash2 = {
            let x: E2 = E2::First(100);
            let mut hasher = DefaultHasher::new();
            x.hash(&mut hasher);
            hasher.finish()
        };
        assert!(hash1 == hash2);
        let hash3 = {
            let x: E1 = E1::Second("abc".to_owned());
            let mut hasher = DefaultHasher::new();
            x.hash(&mut hasher);
            hasher.finish()
        };
        let hash4 = {
            let x: E2 = E2::Second("abc");
            let mut hasher = DefaultHasher::new();
            x.hash(&mut hasher);
            hasher.finish()
        };
        assert!(hash3 == hash4);
    }
}

有没有关于这种保证的文档记录?我认为必须提供这样的保证(否则我看不到正确实现HashMapcontains_key()方法的方法,因为参数可以是任何借用的键形式),但我找不到任何文档记录这种保证。


我不确定有没有保证,但是https://doc.rust-lang.org/src/alloc/string.rs.html#1927-1932。如果这个链接内容发生变化,我会感到惊讶的。 - Veedrac
1个回答

11

是的。这是有保证的,因为String实现了Borrow<str>

Borrow的实现部分约定如下:

此外,在提供其他trait的实现时,需要考虑它们是否应该作为对底层类型的表示而具有与底层类型相同的行为的结果而表现出相同的行为。通常在泛型代码中,当依赖于这些附加trait实现的相同行为时,会使用Borrow<T>。这些trait可能会出现作为附加的trait边界。

特别地,对于借用和拥有的值而言,EqOrdHash必须是等价的:x.borrow() == y.borrow()应该得到与x == y相同的结果。

在标准库中,Borrow 特性在 HashMap::get 中使用。通过 Borrow ,我们可以将 &str 传递给 HashMap<String, _> 上的 get 方法。当然,为了使这个方法正常工作,&String&str 必须为相同的字符串生成相同的哈希值,因此需要满足 Borrow 的要求。这个要求在 HashMap::get 的文档中有所描述:

键可以是地图键类型的任何借用形式,但是所借用形式的 HashEq 必须与键类型的 相同


特性无法在代码中定义这样的要求,因此不符合要求的实现可能存在,因为编译器无法强制执行这些要求。但是,这样的实现会破坏 HashMap 的正确性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接