Type Alias dactyl::NoHash

source ·
pub type NoHash = BuildHasherDefault<NoHasher>;
Expand description

§No-Hash (Passthrough) Hash State.

Hashing can be expensive, and is totally unnecessary for most numeric or pre-hashed types. (You don’t need a hash to tell you that 1_u8 is different than 2_u8!)

NoHash is a drop in replacement for the standard library’s hasher used in HashMap and HashSet that lets the values speak for themselves (e.g. hash(13_u16) == 13_u64), bringing a free performance boost.

This idea isn’t new, but unlike the hashers offered by nohash or prehash, NoHash does not limit itself to primitives or require any custom trait implementations.

It “just works” for any type whose std::hash::Hash implementation writes a single <= 64-bit integer via one of the following:

In other words, NoHash can always be used for i8, i16, i32, i64, u8, u16, u32, u64, all their NonZero and Wrapping counterparts, and any custom types that derive their hashes from one of these types.

(isize and usize will work on most platforms too, just not those with monstrous 128-bit pointer widths.)

§Examples

use dactyl::NoHash;
use std::collections::{HashMap, HashSet};

let mut set: HashSet<u32, NoHash> = HashSet::default();
assert!(set.insert(0_u32));
assert!(set.insert(1_u32));
assert!(set.insert(2_u32));
assert!(! set.insert(2_u32)); // Not unique!

let mut set: HashMap<i8, &str, NoHash> = HashMap::default();
assert_eq!(set.insert(-2_i8, "Hello"), None);
assert_eq!(set.insert(-1_i8, "World"), None);
assert_eq!(set.insert(0_i8, "How"), None);
assert_eq!(set.insert(1_i8, "Are"), None);
assert_eq!(set.insert(1_i8, "You?"), Some("Are")); // Not unique!

This can also be used with custom types that implement Hash in such a way that only a single specialized write_* call occurs.

use dactyl::NoHash;
use std::{
   collections::HashSet,
   hash::{Hash, Hasher},
};

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

impl Eq for Person {}

impl Hash for Person {
    fn hash<H: Hasher>(&self, state: &mut H) {
        state.write_u64(self.id);
        // Note: `self.id.hash(state)` would also work because it just
        // calls `write_u64` under-the-hood.
    }
}

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

let mut set: HashSet<Person, NoHash> = HashSet::default();
assert!(set.insert(Person { name: "Jane".to_owned(), id: 5 }));
assert!(set.insert(Person { name: "Joan".to_owned(), id: 6 }));
assert!(! set.insert(Person { name: "Jack".to_owned(), id: 6 })); // Duplicate ID.

§Panics

NoHash does not support slices, i128, or u128 as they cannot be losslessly converted to u64. If a Hash implementation tries to make use of those write methods, it will panic. On 128-bit platforms, attempts to hash isize or usize will likewise result in a panic.

NoHash will also panic if a Hash implementation writes two or more values to the hasher — as a tuple would, for example — but only for debug builds. When building in release mode, NoHash will simply pass-through the last integer written to it, ignoring everything else.

Aliased Type§

struct NoHash(/* private fields */);