diff --git a/src/hash_map.rs b/src/hash_map.rs index 03688d9..4cce377 100644 --- a/src/hash_map.rs +++ b/src/hash_map.rs @@ -364,6 +364,40 @@ where let hash_map = HashMap::deserialize(deserializer); hash_map.map(|hash_map| Self(hash_map)) } + + fn deserialize_in_place>(deserializer: D, place: &mut Self) -> Result<(), D::Error> { + use serde::de::{MapAccess, Visitor}; + + struct MapInPlaceVisitor<'a, K: 'a, V: 'a>(&'a mut AHashMap); + + impl<'a, 'de, K, V> Visitor<'de> for MapInPlaceVisitor<'a, K, V> + where + K: Deserialize<'de> + Eq + Hash, + V: Deserialize<'de>, + { + type Value = (); + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map") + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + self.0.clear(); + self.0.reserve(map.size_hint().unwrap_or(0).min(4096)); + + while let Some((key, value)) = map.next_entry()? { + self.0.insert(key, value); + } + + Ok(()) + } + } + + deserializer.deserialize_map(MapInPlaceVisitor(place)) + } } #[cfg(test)] @@ -382,8 +416,14 @@ mod test { let mut map = AHashMap::new(); map.insert("for".to_string(), 0); map.insert("bar".to_string(), 1); - let serialization = serde_json::to_string(&map).unwrap(); - let deserialization: AHashMap = serde_json::from_str(&serialization).unwrap(); + let mut serialization = serde_json::to_string(&map).unwrap(); + let mut deserialization: AHashMap = serde_json::from_str(&serialization).unwrap(); + assert_eq!(deserialization, map); + + map.insert("baz".to_string(), 2); + serialization = serde_json::to_string(&map).unwrap(); + let mut deserializer = serde_json::Deserializer::from_str(&serialization); + AHashMap::deserialize_in_place(&mut deserializer, &mut deserialization).unwrap(); assert_eq!(deserialization, map); } } diff --git a/src/hash_set.rs b/src/hash_set.rs index 1d73cf6..eac10af 100644 --- a/src/hash_set.rs +++ b/src/hash_set.rs @@ -313,6 +313,10 @@ where let hash_set = HashSet::deserialize(deserializer); hash_set.map(|hash_set| Self(hash_set)) } + + fn deserialize_in_place>(deserializer: D, place: &mut Self) -> Result<(), D::Error> { + HashSet::deserialize_in_place(deserializer, place) + } } #[cfg(all(test, feature = "serde"))] @@ -324,8 +328,14 @@ mod test { let mut set = AHashSet::new(); set.insert("for".to_string()); set.insert("bar".to_string()); - let serialization = serde_json::to_string(&set).unwrap(); - let deserialization: AHashSet = serde_json::from_str(&serialization).unwrap(); + let mut serialization = serde_json::to_string(&set).unwrap(); + let mut deserialization: AHashSet = serde_json::from_str(&serialization).unwrap(); + assert_eq!(deserialization, set); + + set.insert("baz".to_string()); + serialization = serde_json::to_string(&set).unwrap(); + let mut deserializer = serde_json::Deserializer::from_str(&serialization); + AHashSet::deserialize_in_place(&mut deserializer, &mut deserialization).unwrap(); assert_eq!(deserialization, set); } } diff --git a/src/lib.rs b/src/lib.rs index 9964a7c..f4ec909 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,14 +17,24 @@ //! let mut map: HashMap = HashMap::default(); //! map.insert(12, 34); //! ``` -//! For convinence wrappers called `AHashMap` and `AHashSet` are also provided. -//! These to the same thing with slightly less typing. +//! For convenience, both new-type wrappers and type aliases are provided. The new type wrappers are called called `AHashMap` and `AHashSet`. These do the same thing with slightly less typing. +//! The type aliases are called `ahash::HashMap`, `ahash::HashSet` are also provided and alias the +//! std::[HashMap] and std::[HashSet]. Why are there two options? The wrappers are convenient but +//! can't be used where a generic `std::collection::HashMap` is required. +//! //! ```ignore //! use ahash::AHashMap; //! //! let mut map: AHashMap = AHashMap::with_capacity(4); //! map.insert(12, 34); //! map.insert(56, 78); +//! // There are also type aliases provieded together with some extension traits to make +//! // it more of a drop in replacement for the std::HashMap/HashSet +//! use ahash::{HashMapExt, HashSetExt}; // Used to get with_capacity() +//! let mut map = ahash::HashMap::with_capacity(10); +//! map.insert(12, 34); +//! let mut set = ahash::HashSet::with_capacity(10); +//! set.insert(10); //! ``` #![deny(clippy::correctness, clippy::complexity, clippy::perf)] #![allow(clippy::pedantic, clippy::cast_lossless, clippy::unreadable_literal)] @@ -37,13 +47,27 @@ mod convert; #[cfg(any( all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)), - all(any(target_arch = "arm", target_arch = "aarch64"), target_feature = "crypto", not(miri), feature = "stdsimd") + all( + any(target_arch = "arm", target_arch = "aarch64"), + target_feature = "crypto", + not(miri), + feature = "stdsimd" + ) ))] mod aes_hash; mod fallback_hash; #[cfg(test)] mod hash_quality_test; +#[cfg(feature = "std")] +/// [Hasher]: std::hash::Hasher +/// [HashMap]: std::collections::HashMap +/// Type alias for [HashMap] +pub type HashMap = std::collections::HashMap; +#[cfg(feature = "std")] +/// Type alias for [HashSet] +pub type HashSet = std::collections::HashSet; + #[cfg(feature = "std")] mod hash_map; #[cfg(feature = "std")] @@ -54,13 +78,23 @@ mod specialize; #[cfg(any( all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)), - all(any(target_arch = "arm", target_arch = "aarch64"), target_feature = "crypto", not(miri), feature = "stdsimd") + all( + any(target_arch = "arm", target_arch = "aarch64"), + target_feature = "crypto", + not(miri), + feature = "stdsimd" + ) ))] pub use crate::aes_hash::AHasher; #[cfg(not(any( all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "aes", not(miri)), - all(any(target_arch = "arm", target_arch = "aarch64"), target_feature = "crypto", not(miri), feature = "stdsimd") + all( + any(target_arch = "arm", target_arch = "aarch64"), + target_feature = "crypto", + not(miri), + feature = "stdsimd" + ) )))] pub use crate::fallback_hash::AHasher; pub use crate::random_state::RandomState; @@ -75,6 +109,54 @@ use core::hash::BuildHasher; use core::hash::Hash; use core::hash::Hasher; +#[cfg(feature = "std")] +/// A convenience trait that can be used together with the type aliases defined to +/// get access to the `new()` and `with_capacity()` methods for the HashMap type alias. +pub trait HashMapExt { + /// Constructs a new HashMap + fn new() -> Self; + /// Constructs a new HashMap with a given initial capacity + fn with_capacity(capacity: usize) -> Self; +} + +#[cfg(feature = "std")] +/// A convenience trait that can be used together with the type aliases defined to +/// get access to the `new()` and `with_capacity()` methods for the HashSet type aliases. +pub trait HashSetExt { + /// Constructs a new HashSet + fn new() -> Self; + /// Constructs a new HashSet with a given initial capacity + fn with_capacity(capacity: usize) -> Self; +} + +#[cfg(feature = "std")] +impl HashMapExt for std::collections::HashMap +where + S: BuildHasher + Default, +{ + fn new() -> Self { + std::collections::HashMap::with_hasher(S::default()) + } + + fn with_capacity(capacity: usize) -> Self { + std::collections::HashMap::with_capacity_and_hasher(capacity, S::default()) + } +} + +#[cfg(feature = "std")] +impl HashSetExt for std::collections::HashSet +where + S: BuildHasher + Default, +{ + fn new() -> Self { + std::collections::HashSet::with_hasher(S::default()) + } + + fn with_capacity(capacity: usize) -> Self { + std::collections::HashSet::with_capacity_and_hasher(capacity, S::default()) + } +} + /// Provides a default [Hasher] with fixed keys. /// This is typically used in conjunction with [BuildHasherDefault] to create /// [AHasher]s in order to hash the keys of the map. @@ -198,6 +280,18 @@ mod test { use std::collections::HashMap; use std::hash::Hash; + #[test] + fn test_ahash_alias_map_construction() { + let mut map = super::HashMap::with_capacity(1234); + map.insert(1, "test"); + } + + #[test] + fn test_ahash_alias_set_construction() { + let mut set = super::HashSet::with_capacity(1234); + set.insert(1); + } + #[test] fn test_default_builder() { use core::hash::BuildHasherDefault; @@ -219,7 +313,6 @@ mod test { assert_eq!(bytes, 0x6464646464646464); } - #[test] fn test_non_zero() { let mut hasher1 = AHasher::new_with_keys(0, 0); @@ -241,7 +334,7 @@ mod test { #[test] fn test_non_zero_specialized() { - let hasher_build = RandomState::with_seeds(0,0,0,0); + let hasher_build = RandomState::with_seeds(0, 0, 0, 0); let h1 = str::get_hash("foo", &hasher_build); let h2 = str::get_hash("bar", &hasher_build); diff --git a/tests/map_tests.rs b/tests/map_tests.rs index be617a2..3647169 100644 --- a/tests/map_tests.rs +++ b/tests/map_tests.rs @@ -169,6 +169,27 @@ fn test_bucket_distribution() { check_for_collisions(&build_hasher, &sequence, 256); } +#[cfg(feature = "std")] +#[test] +fn test_ahash_alias_map_construction() { + let mut map = ahash::HashMap::default(); + map.insert(1, "test"); + use ahash::HashMapExt; + let mut map = ahash::HashMap::with_capacity(1234); + map.insert(1, "test"); +} + +#[cfg(feature = "std")] +#[test] +fn test_ahash_alias_set_construction() { + let mut set = ahash::HashSet::default(); + set.insert(1); + + use ahash::HashSetExt; + let mut set = ahash::HashSet::with_capacity(1235); + set.insert(1); +} + fn ahash_vec(b: &Vec) -> u64 { let mut total: u64 = 0; for item in b {