use std::hash::Hash;
pub trait ShardKey: Copy + Eq + Hash + Send + 'static {
fn shard_hash(&self) -> u64;
}
pub trait RequestId: Clone + Eq + Hash + Send + 'static {}
impl<T: Clone + Eq + Hash + Send + 'static> RequestId for T {}
#[inline]
pub const fn mix(mut hash: u64) -> u64 {
hash ^= hash >> 33;
hash = hash.wrapping_mul(0xff51_afd7_ed55_8ccd);
hash ^= hash >> 33;
hash = hash.wrapping_mul(0xc4ce_b9fe_1a85_ec53);
hash ^= hash >> 33;
hash
}
macro_rules! integer_keys {
($($ty:ty),* $(,)?) => {
$(
impl ShardKey for $ty {
#[inline]
fn shard_hash(&self) -> u64 {
*self as u64
}
}
)*
};
}
integer_keys!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize);
impl ShardKey for u128 {
#[inline]
fn shard_hash(&self) -> u64 {
(*self as u64) ^ ((*self >> 64) as u64)
}
}
impl ShardKey for i128 {
#[inline]
fn shard_hash(&self) -> u64 {
(*self as u128).shard_hash()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn mixing_spreads_sequential_keys_across_buckets() {
const BUCKETS: u64 = 16;
let mut counts = HashMap::new();
for key in 0..16_000u64 {
*counts.entry(mix(key.shard_hash()) % BUCKETS).or_insert(0usize) += 1;
}
assert_eq!(counts.len(), BUCKETS as usize, "every bucket must be reachable");
let fair = 16_000 / BUCKETS as usize;
for count in counts.values() {
assert!(*count > fair / 2 && *count < fair * 2, "bucket skew: {count} vs {fair}");
}
}
#[test]
fn integer_and_wide_keys_carry_their_identity() {
assert_eq!(7u64.shard_hash(), 7);
assert_eq!(7usize.shard_hash(), 7);
assert_eq!((-1i32).shard_hash(), u64::from(u32::MAX) | 0xffff_ffff_0000_0000);
assert_eq!(0u128.shard_hash(), 0);
assert_eq!((u128::from(u64::MAX) + 1).shard_hash(), 1, "the high half folds in");
assert_eq!(5i128.shard_hash(), 5);
}
#[test]
fn mixing_is_deterministic_and_injective_on_a_sample() {
let mut seen = HashMap::new();
for key in 0..10_000u64 {
assert_eq!(mix(key), mix(key));
assert!(seen.insert(mix(key), key).is_none(), "collision at {key}");
}
}
#[test]
fn the_mixer_reproduces_fmix64_exactly() {
assert_eq!(mix(0), 0, "the finalizer fixes zero");
assert_eq!(mix(1), 0xb456_bcfc_34c2_cb2c);
assert_eq!(mix(2), 0x3abf_2a20_6506_83e7);
assert_eq!(mix(42), 0x8108_7960_8e42_59cc);
assert_eq!(mix(1000), 0xaf00_2c11_4878_da41);
assert_eq!(mix(u64::MAX), 0x64b5_720b_4b82_5f21);
}
#[test]
fn a_wide_key_folds_its_halves_together_rather_than_merging_their_bits() {
let key = (1u128 << 64) | 3;
assert_eq!(key.shard_hash(), 2, "the halves are combined with xor");
}
}