From f990ea276d36cf9cb212c4963d4462a8f09ebd94 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 15 Feb 2022 19:47:46 +0300 Subject: [PATCH] Add a way to seed `FxHasher` with random seeds This commit adds an optional dependency on `rand` (behind a `rand`) feature that allows seeding `FxHasher` with random seeds. This is done via `FxRandomState` that implemented similar to `std::collections::hash_map::RandomState`. `FxHashMapRnd` and `FxHashSetRnd` are also introduced as type aliases to `HashMap` and `HashSet` with `S = FxRandomState`. --- Cargo.toml | 6 +++++- src/lib.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c1fe4a5..bf74312 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,5 +9,9 @@ keywords = ["hash", "fxhash", "rustc"] repository = "https://github.com/rust-lang-nursery/rustc-hash" [features] -std = [] default = ["std"] +std = [] +rand = ["dep:rand", "std"] + +[dependencies] +rand = { version = "0.8", optional = true } diff --git a/src/lib.rs b/src/lib.rs index e0f3b0a..0b06954 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,9 @@ #[cfg(feature = "std")] extern crate std; +#[cfg(feature = "rand")] +extern crate rand; + use core::convert::TryInto; use core::default::Default; #[cfg(feature = "std")] @@ -46,6 +49,14 @@ pub type FxHashMap = HashMap>; #[cfg(feature = "std")] pub type FxHashSet = HashSet>; +/// Type alias for a hashmap using the `fx` hash algorithm with [`FxRandomState`]. +#[cfg(feature = "rand")] +pub type FxHashMapRand = HashMap; + +/// Type alias for a hashmap using the `fx` hash algorithm with [`FxRandomState`]. +#[cfg(feature = "rand")] +pub type FxHashSetRand = HashSet; + /// A speedy hash algorithm for use within rustc. The hashmap in liballoc /// by default uses SipHash which isn't quite as speedy as we want. In the /// compiler we're not really worried about DOS attempts, so we use a fast @@ -61,6 +72,16 @@ pub struct FxHasher { hash: usize, } +/// `FxRandomState` is an alternative state for `HashMap` types. +/// +/// A particular instance `FxRandomState` will create the same instances of +/// [`Hasher`], but the hashers created by two different `FxRandomState` +/// instances are unlikely to produce the same result for the same values. +#[cfg(feature = "rand")] +pub struct FxRandomState { + seed: usize, +} + #[cfg(target_pointer_width = "32")] const K: usize = 0x9e3779b9; #[cfg(target_pointer_width = "64")] @@ -153,3 +174,38 @@ impl Hasher for FxHasher { self.hash as u64 } } + +#[cfg(feature = "rand")] +impl FxRandomState { + /// Constructs a new `FxRandomState` that is initialized with random seed. + pub fn new() -> FxRandomState { + use rand::Rng; + use std::{cell::Cell, thread_local}; + + // This mirrors what `std::collections::hash_map::RandomState` does, as of 2024-01-14. + // + // Basically + // 1. Cache result of the rng in a thread local, so repeatedly + // creating maps is cheaper + // 2. Change the cached result on every creation, so maps created + // on the same thread don't have the same iteration order + thread_local!(static SEED: Cell = { + Cell::new(rand::thread_rng().gen()) + }); + + SEED.with(|seed| { + let s = seed.get(); + seed.set(s.wrapping_add(1)); + FxRandomState { seed: s } + }) + } +} + +#[cfg(feature = "rand")] +impl core::hash::BuildHasher for FxRandomState { + type Hasher = FxHasher; + + fn build_hasher(&self) -> Self::Hasher { + FxHasher { hash: self.seed } + } +}