Introduce HashLock type

This factors out the memoized hash part of `LazyHash` for manual use
This commit is contained in:
Laurenz 2025-07-07 11:08:15 +02:00
parent d2736918fb
commit 2f401f26e1
2 changed files with 55 additions and 46 deletions

View File

@ -30,9 +30,10 @@ use siphasher::sip128::{Hasher128, SipHasher13};
/// # Usage /// # Usage
/// If the value is expected to be cloned, it is best used inside of an `Arc` /// If the value is expected to be cloned, it is best used inside of an `Arc`
/// or `Rc` to best re-use the hash once it has been computed. /// or `Rc` to best re-use the hash once it has been computed.
#[derive(Clone)]
pub struct LazyHash<T: ?Sized> { pub struct LazyHash<T: ?Sized> {
/// The hash for the value. /// The hash for the value.
hash: AtomicU128, hash: HashLock,
/// The underlying value. /// The underlying value.
value: T, value: T,
} }
@ -48,16 +49,7 @@ impl<T> LazyHash<T> {
/// Wraps an item without pre-computed hash. /// Wraps an item without pre-computed hash.
#[inline] #[inline]
pub fn new(value: T) -> Self { pub fn new(value: T) -> Self {
Self { hash: AtomicU128::new(0), value } Self { hash: HashLock::new(), value }
}
/// Wrap an item with a pre-computed hash.
///
/// **Important:** The hash must be correct for the value. This cannot be
/// enforced at compile time, so use with caution.
#[inline]
pub fn reuse<U: ?Sized>(value: T, existing: &LazyHash<U>) -> Self {
LazyHash { hash: AtomicU128::new(existing.load_hash()), value }
} }
/// Returns the wrapped value. /// Returns the wrapped value.
@ -67,33 +59,11 @@ impl<T> LazyHash<T> {
} }
} }
impl<T: ?Sized> LazyHash<T> {
/// Get the hash, returns zero if not computed yet.
#[inline]
fn load_hash(&self) -> u128 {
// We only need atomicity and no synchronization of other operations, so
// `Relaxed` is fine.
self.hash.load(Ordering::Relaxed)
}
}
impl<T: Hash + ?Sized + 'static> LazyHash<T> { impl<T: Hash + ?Sized + 'static> LazyHash<T> {
/// Get the hash or compute it if not set yet. /// Get the hash or compute it if not set yet.
#[inline] #[inline]
fn load_or_compute_hash(&self) -> u128 { fn load_or_compute_hash(&self) -> u128 {
let mut hash = self.load_hash(); self.hash.get_or_insert_with(|| hash_item(&self.value))
if hash == 0 {
hash = hash_item(&self.value);
self.hash.store(hash, Ordering::Relaxed);
}
hash
}
/// Reset the hash to zero.
#[inline]
fn reset_hash(&mut self) {
// Because we have a mutable reference, we can skip the atomic.
*self.hash.get_mut() = 0;
} }
} }
@ -140,23 +110,14 @@ impl<T: ?Sized> Deref for LazyHash<T> {
} }
} }
impl<T: Hash + ?Sized + 'static> DerefMut for LazyHash<T> { impl<T: ?Sized + 'static> DerefMut for LazyHash<T> {
#[inline] #[inline]
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
self.reset_hash(); self.hash.reset();
&mut self.value &mut self.value
} }
} }
impl<T: Hash + Clone + 'static> Clone for LazyHash<T> {
fn clone(&self) -> Self {
Self {
hash: AtomicU128::new(self.load_hash()),
value: self.value.clone(),
}
}
}
impl<T: Debug> Debug for LazyHash<T> { impl<T: Debug> Debug for LazyHash<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.value.fmt(f) self.value.fmt(f)
@ -233,3 +194,51 @@ impl<T: Debug> Debug for ManuallyHash<T> {
self.value.fmt(f) self.value.fmt(f)
} }
} }
/// Storage for lazy hash computation.
pub struct HashLock(AtomicU128);
impl HashLock {
/// Create a new unset hash cell.
pub const fn new() -> Self {
Self(AtomicU128::new(0))
}
/// Get the hash or compute it if not set yet.
#[inline]
pub fn get_or_insert_with(&self, f: impl FnOnce() -> u128) -> u128 {
let mut hash = self.get();
if hash == 0 {
hash = f();
self.0.store(hash, Ordering::Relaxed);
}
hash
}
/// Reset the hash to unset.
#[inline]
pub fn reset(&mut self) {
// Because we have a mutable reference, we can skip the atomic.
*self.0.get_mut() = 0;
}
/// Get the hash, returns zero if not computed yet.
#[inline]
fn get(&self) -> u128 {
// We only need atomicity and no synchronization of other operations, so
// `Relaxed` is fine.
self.0.load(Ordering::Relaxed)
}
}
impl Default for HashLock {
fn default() -> Self {
Self::new()
}
}
impl Clone for HashLock {
fn clone(&self) -> Self {
Self(AtomicU128::new(self.get()))
}
}

View File

@ -15,7 +15,7 @@ mod scalar;
pub use self::bitset::{BitSet, SmallBitSet}; pub use self::bitset::{BitSet, SmallBitSet};
pub use self::deferred::Deferred; pub use self::deferred::Deferred;
pub use self::duration::format_duration; pub use self::duration::format_duration;
pub use self::hash::{LazyHash, ManuallyHash}; pub use self::hash::{HashLock, LazyHash, ManuallyHash};
pub use self::pico::{PicoStr, ResolvedPicoStr}; pub use self::pico::{PicoStr, ResolvedPicoStr};
pub use self::round::{round_int_with_precision, round_with_precision}; pub use self::round::{round_int_with_precision, round_with_precision};
pub use self::scalar::Scalar; pub use self::scalar::Scalar;