use std::hash::{BuildHasherDefault, Hasher};
const K: usize = 0x517c_c1b7_2722_0a95;
const ROTATE: u32 = 5;
#[derive(Default)]
pub struct FxHasher {
hash: usize,
}
impl FxHasher {
#[inline]
fn add(&mut self, word: usize) {
self.hash = (self.hash.rotate_left(ROTATE) ^ word).wrapping_mul(K);
}
}
impl Hasher for FxHasher {
#[inline]
fn write(&mut self, mut bytes: &[u8]) {
while bytes.len() >= 8 {
let mut b = [0u8; 8];
b.copy_from_slice(&bytes[..8]);
self.add(u64::from_le_bytes(b) as usize);
bytes = &bytes[8..];
}
if bytes.len() >= 4 {
let mut b = [0u8; 4];
b.copy_from_slice(&bytes[..4]);
self.add(u32::from_le_bytes(b) as usize);
bytes = &bytes[4..];
}
if bytes.len() >= 2 {
let mut b = [0u8; 2];
b.copy_from_slice(&bytes[..2]);
self.add(u16::from_le_bytes(b) as usize);
bytes = &bytes[2..];
}
if let Some(&last) = bytes.first() {
self.add(last as usize);
}
}
#[inline]
fn write_u8(&mut self, i: u8) {
self.add(i as usize);
}
#[inline]
fn write_u16(&mut self, i: u16) {
self.add(i as usize);
}
#[inline]
fn write_u32(&mut self, i: u32) {
self.add(i as usize);
}
#[inline]
fn write_u64(&mut self, i: u64) {
self.add(i as usize);
}
#[inline]
fn write_usize(&mut self, i: usize) {
self.add(i);
}
#[inline]
fn finish(&self) -> u64 {
self.hash as u64
}
}
pub type FxBuildHasher = BuildHasherDefault<FxHasher>;
#[inline]
pub fn fx_hash<T: std::hash::Hash>(value: &T) -> u64 {
let mut hasher = FxHasher::default();
value.hash(&mut hasher);
hasher.finish()
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn map_roundtrips() {
let mut m: HashMap<String, u32, FxBuildHasher> = HashMap::default();
for i in 0..1000u32 {
m.insert(format!("key-{i}"), i);
}
for i in 0..1000u32 {
assert_eq!(m.get(&format!("key-{i}")), Some(&i));
}
assert_eq!(m.get("missing"), None);
}
#[test]
fn deterministic_and_distributed() {
assert_eq!(fx_hash(&"example.com"), fx_hash(&"example.com"));
assert_ne!(fx_hash(&"example.com"), fx_hash(&"example.net"));
}
}