diff --git a/Cargo.toml b/Cargo.toml index c865d64..53f7881 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cht" -version = "0.4.0" +version = "0.4.1" authors = ["Gregory Meyer "] edition = "2018" description = "Lockfree resizeable concurrent hash table." diff --git a/README.md b/README.md index f73ac32..becc1db 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,18 @@ [![docs.rs](https://docs.rs/cht/badge.svg)](https://docs.rs/cht) [![Travis CI](https://travis-ci.com/Gregory-Meyer/cht.svg?branch=master)](https://travis-ci.com/Gregory-Meyer/cht) -cht provides a lockfree hash table that supports concurrent lookups, insertions, -and deletions. +cht provides a lockfree hash table that supports fully concurrent lookups, +insertions, modifications, and deletions. The table may also be concurrently +resized to allow more elements to be inserted. cht also provides a segmented +hash table using the same lockfree algorithm for increased concurrent write +performance. ## Usage In your `Cargo.toml`: ```toml -cht = "^0.3.0" +cht = "^0.4.1" ``` Then in your code: diff --git a/src/lib.rs b/src/lib.rs index 371b50b..7d3db3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,14 +22,78 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -//! Lockfree resizeable concurrent hash table. +//! Lockfree hash tables. //! -//! The hash table in this crate was inspired by -//! [a blog post by Jeff Phreshing], which describes the implementation of a -//! hash table in [Junction]. +//! The hash tables in this crate are, at their core, open addressing hash +//! tables implemented using open addressing and boxed buckets. The core of +//! these hash tables are bucket arrays, which consist of a vector of atomic +//! pointers to buckets, an atomic pointer to the next bucket array, and an +//! epoch number. In the context of this crate, an atomic pointer is a nullable +//! pointer that is accessed and manipulated using atomic memory operations. +//! Each bucket consists of a key and a possibly-uninitialized value. +//! +//! The key insight into making the hash table resizeable is to incrementally +//! copy buckets from the old bucket array to the new bucket array. As buckets +//! are copied between bucket arrays, their pointers in the old bucket array are +//! CAS'd with a null pointer that has a sentinel bit set. If the CAS fails, +//! that thread must read the bucket pointer again and retry copying it into the +//! new bucket array. If at any time a thread reads a bucket pointer with the +//! sentinel bit set, that thread knows that a new (larger) bucket array has +//! been allocated. That thread will then immediately attempt to copy all +//! buckets to the new bucket array. It is possible to implement an algorithm in +//! which a subset of buckets are relocated per-thread; such an algorithm has +//! not been implemented for the sake of simplicity. +//! +//! Bucket pointers that have been copied from an old bucket array into a new +//! bucket array are marked with a borrowed bit. If a thread copies a bucket +//! from an old bucket array into a new bucket array, fails to CAS the bucket +//! pointer in the old bucket array, it attempts to CAS the bucket pointer in +//! the new bucket array that it previously inserted to. If the bucket pointer +//! in the new bucket array does *not* have the borrowed tag bit set, that +//! thread knows that the value in the new bucket array was modified more +//! recently than the value in the old bucket array. To avoid discarding updates +//! to the new bucket array, a thread will never replace a bucket pointer that +//! has the borrowed tag bit set with one that does not. To see why this is +//! necessary, consider the case where a bucket pointer is copied into the new +//! array, removed from the new array by a second thread, then copied into the +//! new array again by a third thread. +//! +//! Mutating operations are, at their core, an atomic compare-and-swap (CAS) on +//! a bucket pointer. Insertions CAS null pointers and bucket pointers with +//! matching keys, modifications CAS bucket pointers with matching keys, and +//! removals CAS non-tombstone bucket pointers. Tombstone bucket pointers are +//! bucket pointers with a tombstone bit set as part of a removal; this +//! indicates that the bucket's value has been moved from and will be destroyed +//! if it has not beel already. +//! +//! As previously mentioned, removing an entry from the hash table results in +//! that bucket pointer having a tombstone bit set. Insertions cannot +//! displace a tombstone bucket unless their key compares equal, so once an +//! entry is inserted into the hash table, the specific index it is assigned to +//! will only ever hold entries whose keys compare equal. Without this +//! restriction, resizing operations could result in the old and new bucket +//! arrays being temporarily inconsistent. Consider the case where one thread, +//! as part of a resizing operation, copies a bucket into a new bucket array +//! while another thread removes and replaces that bucket from the old bucket +//! array. If the new bucket has a non-matching key, what happens to the bucket +//! that was just copied into the new bucket array? +//! +//! Tombstone bucket pointers are typically not copied into new bucket arrays. +//! The exception is the case where a bucket pointer was copied to the new +//! bucket array, then CAS on the old bucket array fails because that bucket has +//! been replaced with a tombstone. In this case, the tombstone bucket pointer +//! will be copied over to reflect the update without displacing a key from its +//! bucket. +//! +//! This hash table algorithm was inspired by [a blog post by Jeff Phreshing] +//! that describes the implementation of the Linear hash table in [Junction], a +//! C++ library of concurrent data structrures. Additional inspiration was drawn +//! from the lockfree hash table described by Cliff Click in [a tech talk] given +//! at Google in 2007. //! //! [a blog post by Jeff Phreshing]: https://preshing.com/20160222/a-resizable-concurrent-map/ //! [Junction]: https://github.com/preshing/junction +//! [a tech talk]: https://youtu.be/HJ-719EGIts pub mod map; pub mod segment; diff --git a/src/map.rs b/src/map.rs index 5e8ba88..c98821f 100644 --- a/src/map.rs +++ b/src/map.rs @@ -22,8 +22,8 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -//! A lockfree concurrent hash map implemented with open addressing and linear -//! probing. +//! A lockfree hash map implemented with bucket pointer arrays, open addressing, +//! and linear probing. pub(crate) mod bucket; pub(crate) mod bucket_array_ref; @@ -41,47 +41,45 @@ use ahash::RandomState; use crossbeam_epoch::{self, Atomic}; /// Default hasher for `HashMap`. -/// -/// This is currently [aHash], a hashing algorithm designed around acceleration -/// by the [AES-NI] instruction set on x86 processors. aHash is not -/// cryptographically secure, but is fast and resistant to DoS attacks. Compared -/// to [Fx Hash], the previous default hasher, aHash is slower at hashing -/// integers, faster at hashing strings, and provides DoS attack resistance. -/// -/// [aHash]: https://docs.rs/ahash -/// [AES-NI]: https://en.wikipedia.org/wiki/AES_instruction_set -/// [Fx Hash]: https://docs.rs/fxhash pub type DefaultHashBuilder = RandomState; -/// A lockfree concurrent hash map implemented with open addressing and linear -/// probing. +/// A lockfree hash map implemented with bucket pointer arrays, open addressing, +/// and linear probing. +/// +/// The default hashing algorithm is currently [`AHash`], though this is +/// subject to change at any point in the future. This hash function is very +/// fast for all types of keys, but this algorithm will typically *not* protect +/// against attacks such as HashDoS. /// -/// The default hashing algorithm is [aHash], a hashing algorithm that is -/// accelerated by the [AES-NI] instruction set on x86 proessors. aHash provides -/// some resistance to DoS attacks, but will not provide the same level of -/// resistance as something like [`RandomState`]. +/// The hashing algorithm can be replaced on a per-`HashMap` basis using the +/// [`default`], [`with_hasher`], and [`with_capacity_and_hasher`] methods. Many +/// alternative algorithms are available on crates.io, such as the [`fnv`] crate. /// -/// The hashing algorithm to be used can be chosen on a per-`HashMap` basis -/// using the [`with_hasher`] and [`with_capacity_and_hasher`] methods. +/// It is required that the keys implement the [`Eq`] and [`Hash`] traits, +/// although this can frequently be achieved by using +/// `#[derive(PartialEq, Eq, Hash)]`. If you implement these yourself, it is +/// important that the following property holds: /// -/// Key types must implement [`Hash`] and [`Eq`]. Any operations that return a -/// key or value require the return types to implement [`Clone`], as elements -/// may be in use by other threads and as such cannot be moved from. +/// ```text +/// k1 == k2 -> hash(k1) == hash(k2) +/// ``` /// -/// `HashMap` is inspired by Jeff Phreshing's hash tables implemented in -/// [Junction], described in [this blog post]. In short, `HashMap` supports -/// fully concurrent lookups, insertions, removals, and updates. +/// In other words, if two keys are equal, their hashes must be equal. /// -/// [aHash]: https://docs.rs/ahash -/// [AES-NI]: https://en.wikipedia.org/wiki/AES_instruction_set -/// [`RandomState`]: https://doc.rust-lang.org/std/collections/hash_map/struct.RandomState.html +/// It is a logic error for a key to be modified in such a way that the key's +/// hash, as determined by the [`Hash`] trait, or its equality, as determined by +/// the [`Eq`] trait, changes while it is in the map. This is normally only +/// possible through [`Cell`], [`RefCell`], global state, I/O, or unsafe code. +/// +/// [`AHash`]: https://crates.io/crates/ahash +/// [`fnv`]: https://crates.io/crates/fnv +/// [`default`]: #method.default /// [`with_hasher`]: #method.with_hasher /// [`with_capacity_and_hasher`]: #method.with_capacity_and_hasher -/// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html -/// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html -/// [Junction]: https://github.com/preshing/junction -/// [this blog post]: https://preshing.com/20160222/a-resizable-concurrent-map/ +/// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html +/// [`Cell`]: https://doc.rust-lang.org/std/cell/struct.Ref.html +/// [`RefCell`]: https://doc.rust-lang.org/std/cell/struct.RefCell.html #[derive(Default)] pub struct HashMap { bucket_array: Atomic>, @@ -92,34 +90,38 @@ pub struct HashMap { impl HashMap { /// Creates an empty `HashMap`. /// - /// The hash map is created with a capacity of 0 and will not allocate any - /// space for elements until the first insertion. + /// The hash map is initially created with a capacity of 0, so it will not + /// allocate a bucket pointer array until it is first inserted into. pub fn new() -> HashMap { HashMap::with_capacity_and_hasher(0, DefaultHashBuilder::default()) } - /// Creates an empty `HashMap` with space for at least `capacity` elements - /// without reallocating. + /// Creates an empty `HashMap` with the specified capacity. /// - /// If `capacity == 0`, no allocations will occur. + /// The hash map will be able to hold at least `capacity` elements without + /// reallocating its bucket pointer array. If `capacity` is 0, the hash map + /// will not allocate. pub fn with_capacity(capacity: usize) -> HashMap { HashMap::with_capacity_and_hasher(capacity, DefaultHashBuilder::default()) } } impl HashMap { - /// Creates an empty `HashMap` that will use `build_hasher` to hash keys. + /// Creates an empty `HashMap` which will use the given hash builder to hash + /// keys. /// - /// The created map will have a capacity of 0 and as such will not have any - /// space for elements allocated until the first insertion. + /// The hash map is initially created with a capacity of 0, so it will not + /// allocate a bucket pointer array until it is first inserted into. pub fn with_hasher(build_hasher: S) -> HashMap { HashMap::with_capacity_and_hasher(0, build_hasher) } - /// Creates an empty `HashMap` that will hold at least `capacity` elements - /// without reallocating and that uses `build_hasher` to hash keys. + /// Creates an empty `HashMap` with the specified capacity, using + /// `build_hasher` to hash the keys. /// - /// If `capacity == 0`, no allocations will occur. + /// The hash map will be able to hold at least `capacity` elements without + /// reallocating its bucket pointer array. If `capacity` is 0, the hash map + /// will not allocate. pub fn with_capacity_and_hasher(capacity: usize, build_hasher: S) -> HashMap { let bucket_array = if capacity == 0 { Atomic::null() @@ -137,31 +139,36 @@ impl HashMap { } } - /// Returns the number of elements that are confirmed to have been inserted - /// into this map. + /// Returns the number of elements in the map. + /// + /// # Safety /// - /// Because `HashMap` can be updated concurrently, this function reflects - /// the number of insert operations that have returned to the user. - /// In-progress insertions are not counted. + /// This method on its own is safe, but other threads can add or remove + /// elements at any time. pub fn len(&self) -> usize { self.len.load(Ordering::Relaxed) } - /// Returns true if this `HashMap` contains no confirmed inserted elements. + /// Returns `true` if the map contains no elements. /// - /// In-progress insertions into this `HashMap` are not considered. + /// # Safety + /// + /// This method on its own is safe, but other threads can add or remove + /// elements at any time. pub fn is_empty(&self) -> bool { self.len() == 0 } - /// Returns the number of elements this `HashMap` can hold without - /// reallocating a table. + /// Returns the number of elements the map can hold without reallocating its + /// bucket pointer array. + /// + /// Note that all mutating operations except removal will result in a bucket + /// being allocated or reallocated. /// - /// Note that all mutating operations, with the exception of removing - /// elements, incur at least one allocation for the associated bucket. + /// # Safety /// - /// If there are insertion operations in flight, it is possible that a - /// new, larger table has already been allocated. + /// This method on its own is safe, but other threads can increase the + /// capacity at any time by adding elements. pub fn capacity(&self) -> usize { let guard = &crossbeam_epoch::pin(); @@ -174,17 +181,14 @@ impl HashMap { } impl HashMap { - /// Returns a copy of the value corresponding to `key`. + /// Returns a clone of the value corresponding to the key. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. `V` must implement [`Clone`], as the value may - /// be deleted at any moment; the best we can do is to clone them while we - /// know they exist. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html - /// [`get_and`]: #method.get_and #[inline] pub fn get(&self, key: &Q) -> Option where @@ -194,17 +198,15 @@ impl HashMap { self.get_key_value_and(key, |_, v| v.clone()) } - /// Returns a copy of the key and value corresponding to `key`. + /// Returns a clone of the the key-value pair corresponding to the supplied + /// key. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. `K` and `V` must implement [`Clone`], as the - /// bucket may be concurrently removed at any time; the best we can do is to - /// clone them while we know they exist. + /// The supplied key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for the key + /// type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html - /// [`get_key_value_and`]: #method.get_key_value_and #[inline] pub fn get_key_value(&self, key: &Q) -> Option<(K, V)> where @@ -214,13 +216,12 @@ impl HashMap { self.get_key_value_and(key, |k, v| (k.clone(), v.clone())) } - /// Invokes `with_value` with a reference to the value corresponding to `key`. + /// Returns the result of invoking a function with a reference to the value + /// corresponding to the key. /// - /// `with_value` will only be invoked if there is a value associated with - /// `key` contained within this hash map. - /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html @@ -236,14 +237,12 @@ impl HashMap { self.get_key_value_and(key, move |_, v| with_value(v)) } - /// Invokes `with_entry` with a reference to the key and value corresponding - /// to `key`. - /// - /// `with_entry` will only be invoked if there is a value associated with `key` - /// contained within this hash map. + /// Returns the result of invoking a function with a reference to the + /// key-value pair corresponding to the supplied key. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. + /// The supplied key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for the key + /// type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html @@ -262,17 +261,11 @@ impl HashMap { .get_key_value_and(key, hash, with_entry) } - /// Inserts a key-value pair, then returns a copy of the value previously - /// associated with `key`. - /// - /// If the key was not previously present in this hash map, [`None`] is - /// returned. - /// - /// `V` must implement [`Clone`], as other threads may hold references to - /// the associated value. + /// Inserts a key-value pair into the map, returning a clone of the value + /// previously corresponding to the key. /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html + /// If the map did have this key present, both the key and value are + /// updated. #[inline] pub fn insert(&self, key: K, value: V) -> Option where @@ -281,16 +274,11 @@ impl HashMap { self.insert_entry_and(key, value, |_, v| v.clone()) } - /// Inserts a key-value pair, then returns a copy of the previous entry. - /// - /// If the key was not previously present in this hash map, [`None`] is - /// returned. + /// Inserts a key-value pair into the map, returning a clone of the + /// key-value pair previously corresponding to the supplied key. /// - /// `K` and `V` must implement [`Clone`], as other threads may hold - /// references to the entry. - /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html + /// If the map did have this key present, both the key and value are + /// updated. #[inline] pub fn insert_entry(&self, key: K, value: V) -> Option<(K, V)> where @@ -300,13 +288,12 @@ impl HashMap { self.insert_entry_and(key, value, |k, v| (k.clone(), v.clone())) } - /// Inserts a key-value pair, then invokes `with_previous_value` with the - /// value previously associated with `key`. + /// Inserts a key-value pair into the map, returning the result of invoking + /// a function with a reference to the value previously corresponding to the + /// key. /// - /// If the key was not previously present in this hash map, [`None`] is - /// returned and `with_previous_value` is not invoked. - /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// If the map did have this key present, both the key and value are + /// updated. #[inline] pub fn insert_and T, T>( &self, @@ -317,13 +304,12 @@ impl HashMap { self.insert_entry_and(key, value, move |_, v| with_previous_value(v)) } - /// Inserts a key-value pair, then invokes `with_previous_entry` with the - /// previous entry. - /// - /// If the key was not previously present in this hash map, [`None`] is - /// returned and `with_previous_entry` is not invoked. + /// Inserts a key-value pair into the map, returning the result of invoking + /// a function with a reference to the key-value pair previously + /// corresponding to the supplied key. /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// If the map did have this key present, both the key and value are + /// updated. #[inline] pub fn insert_entry_and T, T>( &self, @@ -337,16 +323,15 @@ impl HashMap { .insert_entry_and(key, hash, value, with_previous_entry) } - /// If there is a value associated with `key`, remove and return a copy of - /// it. + /// Removes a key from the map, returning a clone of the value previously + /// corresponding to the key. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. `V` must implement [`Clone`], as other - /// threads may hold references to the associated value. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html #[inline] pub fn remove(&self, key: &Q) -> Option where @@ -356,16 +341,15 @@ impl HashMap { self.remove_entry_if_and(key, |_, _| true, |_, v| v.clone()) } - /// If there is a value associated with `key`, remove it and return a copy - /// of the previous entity. + /// Removes a key from the map, returning a clone of the key-value pair + /// previously corresponding to the key. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. `K` and `V` must implement [`Clone`], as other - /// threads may hold references to the entry. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html #[inline] pub fn remove_entry(&self, key: &Q) -> Option<(K, V)> where @@ -375,11 +359,12 @@ impl HashMap { self.remove_entry_if_and(key, |_, _| true, |k, v| (k.clone(), v.clone())) } - /// If there is a value associated with `key`, remove it and return the - /// result of invoking `with_previous_value` with that value. + /// Remove a key from the map, returning the result of invoking a function + /// with a reference to the value previously corresponding to the key. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html @@ -395,11 +380,13 @@ impl HashMap { self.remove_entry_if_and(key, |_, _| true, move |_, v| with_previous_value(v)) } - /// If there is a value associated with `key`, remove it and return the - /// result of invoking `with_previous_entry` with that entry. + /// Removes a key from the map, returning the result of invoking a function + /// with a reference to the key-value pair previously corresponding to the + /// key. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html @@ -415,21 +402,20 @@ impl HashMap { self.remove_entry_if_and(key, |_, _| true, with_previous_entry) } - /// If there is a value associated with `key` and `condition` returns true - /// when invoked with the current entry, remove and return a copy of its - /// value. + /// Removes a key from the map if a condition is met, returning a clone of + /// the value previously corresponding to the key. /// - /// `condition` may be invoked one or more times, even if no entry was - /// removed. + /// `condition` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. `K` and `V` must implement [`Clone`], as other - /// threads may hold references to the entry. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html - #[inline] + /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None pub fn remove_if bool>( &self, key: &Q, @@ -442,19 +428,20 @@ impl HashMap { self.remove_entry_if_and(key, condition, move |_, v| v.clone()) } - /// If there is a value associated with `key` and `condition` returns true - /// when invoked with the current entry, remove and return a copy of it. + /// Removes a key from the map if a condition is met, returning a clone of + /// the key-value pair previously corresponding to the key. /// - /// `condition` may be invoked one or more times, even if no entry was - /// removed. + /// `condition` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. `K` and `V` must implement [`Clone`], as other - /// threads may hold references to the entry. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html + /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn remove_entry_if bool>( &self, @@ -468,19 +455,20 @@ impl HashMap { self.remove_entry_if_and(key, condition, move |k, v| (k.clone(), v.clone())) } - /// If there is a value associated with `key` and `condition` returns true - /// when invoked with the current entry, remove it and return the result of - /// invoking `with_previous_value` with its value. + /// Remove a key from the map if a condition is met, returning the result of + /// invoking a function with a reference to the value previously + /// corresponding to the key. /// - /// `condition` may be invoked one or more times, even if no entry was - /// removed. If `condition` failed or there was no value associated with - /// `key`, `with_previous_entry` is not invoked and [`None`] is returned. + /// `condition` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html + /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn remove_if_and bool, G: FnOnce(&V) -> T, T>( @@ -495,19 +483,20 @@ impl HashMap { self.remove_entry_if_and(key, condition, move |_, v| with_previous_value(v)) } - /// If there is a value associated with `key` and `condition` returns true - /// when invoked with the current entry, remove it and return the result of - /// invoking `with_previous_entry` with it. + /// Removes a key from the map if a condition is met, returning the result + /// of invoking a function with a reference to the key-value pair previously + /// corresponding to the key. /// - /// `condition` may be invoked one or more times, even if no entry was - /// removed. If `condition` failed or there was no value associated with - /// `key`, `with_previous_entry` is not invoked and [`None`] is returned. + /// `condition` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html + /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn remove_entry_if_and< @@ -530,18 +519,15 @@ impl HashMap { .remove_entry_if_and(key, hash, condition, with_previous_entry) } - /// Insert a value if none is associated with `key`. Otherwise, replace the - /// value with the result of `on_modify` with the current entry as - /// arguments. Finally, return a copy of the previously associated value. + /// If no value corresponds to the key, insert a new key-value pair into + /// the map. Otherwise, modify the existing value and return a clone of the + /// value previously corresponding to the key. /// - /// If there is no value associated with `key`, [`None`] will be returned. - /// `on_modify` may be invoked multiple times, even if [`None`] is returned. - /// - /// `V` must implement [`Clone`], as other threads may hold references to - /// the associated value. + /// `on_modify` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// + /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html #[inline] pub fn insert_or_modify V>( &self, @@ -555,18 +541,15 @@ impl HashMap { self.insert_with_or_modify_entry_and(key, move || value, on_modify, |_, v| v.clone()) } - /// Insert a value if none is associated with `key`. Otherwise, replace the - /// value with the result of `on_modify` with the current entry as - /// arguments. Finally, return a copy of the previous entry. + /// If no value corresponds to the key, insert a new key-value pair into + /// the map. Otherwise, modify the existing value and return a clone of the + /// key-value pair previously corresponding to the key. /// - /// If there is no value associated with `key`, [`None`] will be returned. - /// `on_modify` may be invoked multiple times, even if [`None`] is returned. - /// - /// `K` and `V` must implement [`Clone`], as other threads may hold - /// references to the entry. + /// `on_modify` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// + /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html #[inline] pub fn insert_or_modify_entry V>( &self, @@ -586,22 +569,17 @@ impl HashMap { ) } - /// Insert the result of `on_insert` if no value is associated with `key`. - /// Otherwise, replace the value with the result of `on_modify` with the - /// current entry as arguments. Finally, return a copy of the previously - /// associated value. + /// If no value corresponds to the key, invoke a default function to insert + /// a new key-value pair into the map. Otherwise, modify the existing value + /// and return a clone of the value previously corresponding to the key. /// - /// If there is no value associated with `key`, `on_insert` will be invoked - /// and [`None`] will be returned. `on_modify` may be invoked multiple - /// times, even if [`None`] is returned. Similarly, `on_insert` may be - /// invoked if [`Some`] is returned. + /// `on_insert` may be invoked, even if [`None`] is returned. /// - /// `V` must implement [`Clone`], as other threads may hold references to - /// the associated value. + /// `on_modify` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn insert_with_or_modify V, G: FnMut(&K, &V) -> V>( &self, @@ -615,22 +593,18 @@ impl HashMap { self.insert_with_or_modify_entry_and(key, on_insert, on_modify, |_, v| v.clone()) } - /// Insert the result of `on_insert` if no value is associated with `key`. - /// Otherwise, replace the value with the result of `on_modify` with the - /// current entry as arguments. Finally, return a copy of the previous - /// entry. + /// If no value corresponds to the key, invoke a default function to insert + /// a new key-value pair into the map. Otherwise, modify the existing value + /// and return a clone of the key-value pair previously corresponding to the + /// key. /// - /// If there is no value associated with `key`, `on_insert` will be invoked - /// and [`None`] will be returned. `on_modify` may be invoked multiple - /// times, even if [`None`] is returned. Similarly, `on_insert` may be - /// invoked if [`Some`] is returned. + /// `on_insert` may be invoked, even if [`None`] is returned. /// - /// `K` and `V` must implement [`Clone`], as other threads may hold - /// references to the entry. + /// `on_modify` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn insert_with_or_modify_entry V, G: FnMut(&K, &V) -> V>( &self, @@ -647,15 +621,15 @@ impl HashMap { }) } - /// Insert a value if none is associated with `key`. Otherwise, replace the - /// value with the result of `on_modify` with the current entry as - /// arguments. Finally, return the result of invoking `with_old_value` with - /// the previously associated value. + /// If no value corresponds to the key, insert a new key-value pair into + /// the map. Otherwise, modify the existing value and return the result of + /// invoking a function with a reference to the value previously + /// corresponding to the key. /// - /// If there is no value associated with `key`, `with_old_value` will not be - /// invoked and [`None`] will be returned. `on_modify` may be invoked - /// multiple times, even if [`None`] is returned. + /// `on_modify` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// + /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn insert_or_modify_and V, G: FnOnce(&V) -> T, T>( @@ -673,15 +647,15 @@ impl HashMap { ) } - /// Insert a value if none is associated with `key`. Otherwise, replace the - /// value with the result of `on_modify` with the current entry as - /// arguments. Finally, return the result of invoking `with_old_entry` with - /// the previous entry. + /// If no value corresponds to the key, insert a new key-value pair into + /// the map. Otherwise, modify the existing value and return the result of + /// invoking a function with a reference to the key-value pair previously + /// corresponding to the supplied key. /// - /// If there is no value associated with `key`, `with_old_value` will not be - /// invoked and [`None`] will be returned. `on_modify` may be invoked - /// multiple times, even if [`None`] is returned. + /// `on_modify` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// + /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn insert_or_modify_entry_and V, G: FnOnce(&K, &V) -> T, T>( @@ -694,18 +668,18 @@ impl HashMap { self.insert_with_or_modify_entry_and(key, move || value, on_modify, with_old_entry) } - /// Insert the result of `on_insert` if no value is associated with `key`. - /// Otherwise, replace the value with the result of `on_modify` with the - /// current entry as arguments. Finally, return the result of invoking - /// `with_old_value` with the previously associated value. + /// If no value corresponds to the key, invoke a default function to insert + /// a new key-value pair into the map. Otherwise, modify the existing value + /// and return the result of invoking a function with a reference to the + /// value previously corresponding to the key. /// - /// If there is no value associated with `key`, `on_insert` will be invoked, - /// `with_old_value` will not be invoked, and [`None`] will be returned. - /// `on_modify` may be invoked multiple times, even if [`None`] is returned. - /// Similarly, `on_insert` may be invoked if [`Some`] is returned. + /// `on_insert` may be invoked, even if [`None`] is returned. + /// + /// `on_modify` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn insert_with_or_modify_and< F: FnOnce() -> V, @@ -724,18 +698,18 @@ impl HashMap { }) } - /// Insert the result of `on_insert` if no value is associated with `key`. - /// Otherwise, replace the value with the result of `on_modify` with the - /// current entry as arguments. Finally, return the result of invoking - /// `with_old_entry` with the previous entry. + /// If no value corresponds to the key, invoke a default function to insert + /// a new key-value pair into the map. Otherwise, modify the existing value + /// and return the result of invoking a function with a reference to the + /// key-value pair previously corresponding to the supplied key. /// - /// If there is no value associated with `key`, `on_insert` will be invoked, - /// `with_old_value` will not be invoked, and [`None`] will be returned. - /// `on_modify` may be invoked multiple times, even if [`None`] is returned. - /// Similarly, `on_insert` may be invoked if [`Some`] is returned. + /// `on_insert` may be invoked, even if [`None`] is returned. + /// + /// `on_modify` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn insert_with_or_modify_entry_and< F: FnOnce() -> V, @@ -760,21 +734,8 @@ impl HashMap { ) } - /// If there is a value associated with `key`, replace it with the result of - /// invoking `on_modify` using the current key and value, then return a copy - /// of the previously associated value. - /// - /// If there is no value associated with `key`, [`None`] will be returned. - /// `on_modify` may be invoked multiple times, even if [`None`] is returned. - /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. `V` must implement [`Clone`], as other - /// threads may hold references to the associated value. - /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None - /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html - /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html + /// Modifies the value corresponding to a key, returning a clone of the + /// value previously corresponding to that key. #[inline] pub fn modify V>(&self, key: K, on_modify: F) -> Option where @@ -783,21 +744,8 @@ impl HashMap { self.modify_entry_and(key, on_modify, |_, v| v.clone()) } - /// If there is a value associated with `key`, replace it with the result of - /// invoking `on_modify` using the current key and value, then return a copy - /// of the previously entry. - /// - /// If there is no value associated with `key`, [`None`] will be returned. - /// `on_modify` may be invoked multiple times, even if [`None`] is returned. - /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. `K` and `V` must implement [`Clone`], as other - /// threads may hold references to the entry. - /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None - /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html - /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html + /// Modifies the value corresponding to a key, returning a clone of the + /// key-value pair previously corresponding to that key. #[inline] pub fn modify_entry V>(&self, key: K, on_modify: F) -> Option<(K, V)> where @@ -807,21 +755,9 @@ impl HashMap { self.modify_entry_and(key, on_modify, |k, v| (k.clone(), v.clone())) } - /// If there is a value associated with `key`, replace it with the result of - /// invoking `on_modify` using the current key and value, then return the - /// result of invoking `with_old_value` with the previously associated - /// value. - /// - /// If there is no value associated with `key`, `with_old_value` will not be - /// invoked and [`None`] will be returned. `on_modify` may be invoked - /// multiple times, even if [`None`] is returned. - /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. - /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None - /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html - /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html + /// Modifies the value corresponding to a key, returning the result of + /// invoking a function with a reference to the value previously + /// corresponding to the key. #[inline] pub fn modify_and V, G: FnOnce(&V) -> T, T>( &self, @@ -832,20 +768,9 @@ impl HashMap { self.modify_entry_and(key, on_modify, move |_, v| with_old_value(v)) } - /// If there is a value associated with `key`, replace it with the result of - /// invoking `on_modify` using the current key and value, then return the - /// result of invoking `with_old_value` with the previous entry. - /// - /// If there is no value associated with `key`, `with_old_value` will not be - /// invoked and [`None`] will be returned. `on_modify` may be invoked - /// multiple times, even if [`None`] is returned. - /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. - /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None - /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html - /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html + /// Modifies the value corresponding to a key, returning the result of + /// invoking a function with a reference to the key-value pair previously + /// corresponding to the supplied key. #[inline] pub fn modify_entry_and V, G: FnOnce(&K, &V) -> T, T>( &self, diff --git a/src/segment.rs b/src/segment.rs index de96124..fba0da0 100644 --- a/src/segment.rs +++ b/src/segment.rs @@ -22,16 +22,36 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -//! Segmented hash tables. +//! Segmented lockfree hash tables. //! -//! Segmented hash tables are divided into a user-defined number of smaller hash -//! maps. The most-significant bits of hashed keys are used to select which -//! segment a key will be inserted to. +//! Segmented hash tables divide their entries between a number of smaller +//! logical hash tables, or segments. Each segment is entirely independent from +//! the others, and entries are never relocated across segment boundaries. //! -//! Compared to the unsegmented hash table in this crate, the segmented hash -//! table has better maximum concurrent write throughput for disjoint sets of -//! keys, but slightly worse read and single-threaded write performance due to -//! the extra layer of indirection introduced by segmenting. +//! In the context of this crate, a segment refers specifically to an array of +//! bucket pointers. The number of segments in a hash table is rounded up to the +//! nearest power of two; this is so that selecting the segment for a key is no +//! more than a right shift to select the most significant bits of a hashed key. +//! +//! Each segment is entirely independent from the others, all operations can be +//! performed concurrently by multiple threads. Should a set of threads be +//! operating on disjoint sets of segments, the only synchronization between +//! them will be destructive interference as they access and update the bucket +//! array pointer and length for each segment. +//! +//! Compared to the unsegmented hash tables in this crate, the segmented hash +//! tables have higher concurrent write throughput for disjoint sets of keys. +//! However, the segmented hash tables have slightly lower read and +//! single-threaded write throughput. This is because the segmenting structure +//! adds another layer of indirection between the hash table and its buckets. +//! +//! The idea for segmenting hash tables was inspired by the +//! [`ConcurrentHashMap`] from OpenJDK 7, which consists of a number of +//! separately-locked segments. OpenJDK 8 introduced a striped concurrent hash +//! map that stripes a set of bucket locks across the set of buckets using the +//! least significant bits of hashed keys. +//! +//! [`ConcurrentHashMap`]: https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/concurrent/ConcurrentHashMap.java pub mod map; diff --git a/src/segment/map.rs b/src/segment/map.rs index a5c80cf..8ca8116 100644 --- a/src/segment/map.rs +++ b/src/segment/map.rs @@ -22,8 +22,8 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -//! Lockfree concurrent segmented hash map implemented with open addressing and -//! linear probing. +//! A lockfree hash map implemented with segmented bucket pointer arrays, open +//! addressing, and linear probing. use crate::map::{ bucket::{self, BucketArray}, @@ -40,49 +40,59 @@ use std::{ use crossbeam_epoch::Atomic; -/// Lockfree concurrent segmented hash map implemented with open addressing and -/// linear probing. +/// A lockfree hash map implemented with segmented bucket pointer arrays, open +/// addressing, and linear probing. /// -/// The default hashing algorithm is [aHash], a hashing algorithm that is -/// accelerated by the [AES-NI] instruction set on x86 proessors. aHash provides -/// some resistance to DoS attacks, but will not provide the same level of -/// resistance as something like [`RandomState`]. +/// The default hashing algorithm is currently [`AHash`], though this is +/// subject to change at any point in the future. This hash function is very +/// fast for all types of keys, but this algorithm will typically *not* protect +/// against attacks such as HashDoS. /// -/// The hashing algorithm to be used can be chosen on a per-`HashMap` basis -/// using the [`with_hasher`], [`with_capacity_and_hasher`], and -/// [`with_num_segments_capacity_and_hasher`] methods. -/// -/// This map is segmented using the most-significant bits of hashed keys, -/// meaning that entries are spread across several smaller hash maps (segments). -/// Changing the number of segments in a map after construction is not -/// supported. +/// The hashing algorithm can be replaced on a per-`HashMap` basis using the +/// [`default`], [`with_hasher`], [`with_capacity_and_hasher`], +/// [`with_num_segments_and_hasher`], and +/// [`with_num_segments_capacity_and_hasher`] methods. Many alternative +/// algorithms are available on crates.io, such as the [`fnv`] crate. /// -/// The minimum number of segments can be specified as a parameter to +/// The number of segments can be specified on a per-`HashMap` basis using the /// [`with_num_segments`], [`with_num_segments_and_capacity`], /// [`with_num_segments_and_hasher`], and -/// [`with_num_segments_capacity_and_hasher`]. By default, the `num-cpus` -/// feature is enabled and [`new`] and [`with_capacity`] will create maps with -/// at least twice as many segments as the system has CPUs. +/// [`with_num_segments_capacity_and_hasher`] methods. By default, the +/// `num-cpus` feature is enabled and [`new`], [`with_capacity`], +/// [`with_hasher`], and [`with_capacity_and_hasher`] will create maps with +/// twice as many segments as the system has CPUs. /// -/// Key types must implement [`Hash`] and [`Eq`]. Any operations that return a -/// key or value require the return types to implement [`Clone`], as elements -/// may be in use by other threads and as such cannot be moved from. +/// It is required that the keys implement the [`Eq`] and [`Hash`] traits, +/// although this can frequently be achieved by using +/// `#[derive(PartialEq, Eq, Hash)]`. If you implement these yourself, it is +/// important that the following property holds: /// -/// [`new`]: #method.new +/// ```text +/// k1 == k2 -> hash(k1) == hash(k2) +/// ``` +/// +/// In other words, if two keys are equal, their hashes must be equal. +/// +/// It is a logic error for a key to be modified in such a way that the key's +/// hash, as determined by the [`Hash`] trait, or its equality, as determined by +/// the [`Eq`] trait, changes while it is in the map. This is normally only +/// possible through [`Cell`], [`RefCell`], global state, I/O, or unsafe code. +/// +/// [`AHash`]: https://crates.io/crates/ahash +/// [`fnv`]: https://crates.io/crates/fnv +/// [`default`]: #method.default +/// [`with_hasher`]: #method.with_hasher /// [`with_capacity`]: #method.with_capacity -/// [`with_num_segments`]: #method.with_num_segments -/// [`with_num_segments_and_capacity`]: #method.with_num_segments_and_capacity +/// [`with_capacity_and_hasher`]: #method.with_capacity_and_hasher /// [`with_num_segments_and_hasher`]: #method.with_num_segments_and_hasher /// [`with_num_segments_capacity_and_hasher`]: #method.with_num_segments_capacity_and_hasher -/// [aHash]: https://docs.rs/ahash -/// [AES-NI]: https://en.wikipedia.org/wiki/AES_instruction_set -/// [`RandomState`]: https://doc.rust-lang.org/std/collections/hash_map/struct.RandomState.html -/// [`with_hasher`]: #method.with_hasher -/// [`with_capacity_and_hasher`]: #method.with_capacity_and_hasher -/// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html +/// [`with_num_segments`]: #method.with_num_segments +/// [`with_num_segments_and_capacity`]: #method.with_num_segments_and_capacity +/// [`new`]: #method.new /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html -/// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html -#[derive(Default)] +/// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html +/// [`Cell`]: https://doc.rust-lang.org/std/cell/struct.Ref.html +/// [`RefCell`]: https://doc.rust-lang.org/std/cell/struct.RefCell.html pub struct HashMap { segments: Box<[Segment]>, build_hasher: S, @@ -94,67 +104,93 @@ pub struct HashMap { impl HashMap { /// Creates an empty `HashMap`. /// - /// The hash map is created with a capacity of 0 and no memory for segments - /// will be allocated until the first insertion to each segment. However, - /// memory will always be allocated to store segment pointers and lengths. + /// The hash map is initially created with a capacity of 0, so it will not + /// allocate bucket pointer arrays until it is first inserted into. However, + /// it will always allocate memory for segment pointers and lengths. /// /// The `HashMap` will be created with at least twice as many segments as /// the system has CPUs. pub fn new() -> Self { Self::with_num_segments_capacity_and_hasher( - Self::default_num_segments(), + default_num_segments(), 0, DefaultHashBuilder::default(), ) } - /// Creates an empty `HashMap` with space for at least `capacity` elements - /// without reallocating. + /// Creates an empty `HashMap` with the specified capacity. /// - /// If `capacity == 0`, no memory for segments will be allocated until the - /// first insertion to each segment. However, memory will always be - /// allocated to store segment pointers and lengths. + /// The hash map will be able to hold at least `capacity` elements without + /// reallocating any bucket pointer arrays. If `capacity` is 0, the hash map + /// will not allocate any bucket pointer arrays. However, it will always + /// allocate memory for segment pointers and lengths. /// /// The `HashMap` will be created with at least twice as many segments as /// the system has CPUs. pub fn with_capacity(capacity: usize) -> Self { Self::with_num_segments_capacity_and_hasher( - Self::default_num_segments(), + default_num_segments(), capacity, DefaultHashBuilder::default(), ) } +} - fn default_num_segments() -> usize { - num_cpus::get() * 2 +#[cfg(feature = "num-cpus")] +impl HashMap { + /// Creates an empty `HashMap` which will use the given hash builder to hash + /// keys. + /// + /// The hash map is initially created with a capacity of 0, so it will not + /// allocate bucket pointer arrays until it is first inserted into. However, + /// it will always allocate memory for segment pointers and lengths. + /// + /// The `HashMap` will be created with at least twice as many segments as + /// the system has CPUs. + pub fn with_hasher(build_hasher: S) -> Self { + Self::with_num_segments_capacity_and_hasher(default_num_segments(), 0, build_hasher) + } + + /// Creates an empty `HashMap` with the specified capacity, using + /// `build_hasher` to hash the keys. + /// + /// The hash map will be able to hold at least `capacity` elements without + /// reallocating any bucket pointer arrays. If `capacity` is 0, the hash map + /// will not allocate any bucket pointer arrays. However, it will always + /// allocate memory for segment pointers and lengths. + /// + /// The `HashMap` will be created with at least twice as many segments as + /// the system has CPUs. + pub fn with_capacity_and_hasher(capacity: usize, build_hasher: S) -> Self { + Self::with_num_segments_capacity_and_hasher(default_num_segments(), capacity, build_hasher) } } impl HashMap { - /// Creates an empty `HashMap` with at least `num_segments` segments. + /// Creates an empty `HashMap` with the specified number of segments. /// - /// The hash map is created with a capacity of 0 and no memory for segments - /// will be allocated until the first insertion to each segment. However, - /// memory will always be allocated to store segment pointers and lengths. + /// The hash map is initially created with a capacity of 0, so it will not + /// allocate bucket pointer arrays until it is first inserted into. However, + /// it will always allocate memory for segment pointers and lengths. /// /// # Panics /// - /// Panics if `num_segments == 0`. + /// Panics if `num_segments` is 0. pub fn with_num_segments(num_segments: usize) -> Self { Self::with_num_segments_capacity_and_hasher(num_segments, 0, DefaultHashBuilder::default()) } - /// Creates an empty `HashMap` with at least `num_segments` segments and - /// space for at least `capacity` elements in each segment without - /// reallocating. + /// Creates an empty `HashMap` with the specified number of segments and + /// capacity. /// - /// If `capacity == 0`, no memory for segments will be allocated until the - /// first insertion to each segment. However, memory will always be - /// allocated to store segment pointers and lengths. + /// The hash map will be able to hold at least `capacity` elements without + /// reallocating any bucket pointer arrays. If `capacity` is 0, the hash map + /// will not allocate any bucket pointer arrays. However, it will always + /// allocate memory for segment pointers and lengths. /// /// # Panics /// - /// Panics if `num_segments == 0`. + /// Panics if `num_segments` is 0. pub fn with_num_segments_and_capacity(num_segments: usize, capacity: usize) -> Self { Self::with_num_segments_capacity_and_hasher( num_segments, @@ -165,31 +201,31 @@ impl HashMap { } impl HashMap { - /// Creates an empty `HashMap` that will use `build_hasher` to hash keys - /// with at least `num_segments` segments. + /// Creates an empty `HashMap` with the specified number of segments, using + /// `build_hasher` to hash the keys. /// - /// The hash map is created with a capacity of 0 and no memory for segments - /// will be allocated until the first insertion to each segment. However, - /// memory will always be allocated to store segment pointers and lengths. + /// The hash map is initially created with a capacity of 0, so it will not + /// allocate bucket pointer arrays until it is first inserted into. However, + /// it will always allocate memory for segment pointers and lengths. /// /// # Panics /// - /// Panics if `num_segments == 0`. + /// Panics if `num_segments` is 0. pub fn with_num_segments_and_hasher(num_segments: usize, build_hasher: S) -> Self { Self::with_num_segments_capacity_and_hasher(num_segments, 0, build_hasher) } - /// Creates an empty `HashMap` that will use `build_hasher` to hash keys, - /// hold at least `capacity` elements without reallocating, and have at - /// least `num_segments` segments. + /// Creates an empty `HashMap` with the specified number of segments and + /// capacity, using `build_hasher` to hash the keys. /// - /// If `capacity == 0`, no memory for segments will be allocated until the - /// first insertion to each segment. However, memory will always be - /// allocated to store segment pointers and lengths. + /// The hash map will be able to hold at least `capacity` elements without + /// reallocating any bucket pointer arrays. If `capacity` is 0, the hash map + /// will not allocate any bucket pointer arrays. However, it will always + /// allocate memory for segment pointers and lengths. /// /// # Panics /// - /// Panics if `num_segments == 0`. + /// Panics if `num_segments` is 0. pub fn with_num_segments_capacity_and_hasher( num_segments: usize, capacity: usize, @@ -248,14 +284,11 @@ impl HashMap { self.len() == 0 } - /// Returns the number of elements the map can hold without reallocating a - /// bucket pointer array. + /// Returns the number of elements the map can hold without reallocating any + /// bucket pointer arrays. /// - /// As the map is composed of multiple separately allocated segments, this - /// method returns the minimum capacity of all segments. - /// - /// Note that all mutating operations, with the exception of removing - /// elements, will result in an allocation for a new bucket. + /// Note that all mutating operations except removal will result in a bucket + /// being allocated or reallocated. /// /// # Safety /// @@ -318,15 +351,14 @@ impl HashMap { } impl HashMap { - /// Returns a copy of the value associated with `key`. + /// Returns a clone of the value corresponding to the key. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. `V` must implement [`Clone`], as other threads - /// may hold references to the associated value. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html #[inline] pub fn get(&self, key: &Q) -> Option where @@ -336,15 +368,15 @@ impl HashMap { self.get_key_value_and(key, |_, v| v.clone()) } - /// Returns a copy of the key and value associated with `key`. + /// Returns a clone of the the key-value pair corresponding to the supplied + /// key. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. `K` and `V` must implement [`Clone`], as other - /// threads may hold references to the entry. + /// The supplied key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for the key + /// type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html #[inline] pub fn get_key_value(&self, key: &Q) -> Option<(K, V)> where @@ -354,15 +386,13 @@ impl HashMap { self.get_key_value_and(key, |k, v| (k.clone(), v.clone())) } - /// Invokes `with_value` with a reference to the value associated with `key`. + /// Returns the result of invoking a function with a reference to the value + /// corresponding to the key. /// - /// If there is no value associated with `key` in the map, `with_value` will - /// not be invoked and [`None`] will be returned. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. - /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html #[inline] @@ -377,16 +407,13 @@ impl HashMap { self.get_key_value_and(key, move |_, v| with_value(v)) } - /// Invokes `with_entry` with a reference to the key and value associated - /// with `key`. - /// - /// If there is no value associated with `key` in the map, `with_entry` will - /// not be invoked and [`None`] will be returned. + /// Returns the result of invoking a function with a reference to the + /// key-value pair corresponding to the supplied key. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. + /// The supplied key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for the key + /// type. /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html #[inline] @@ -404,17 +431,11 @@ impl HashMap { .get_key_value_and(key, hash, with_entry) } - /// Inserts a key-value pair, then returns a copy of the value previously - /// associated with `key`. - /// - /// If the key was not previously present in this hash map, [`None`] is - /// returned. - /// - /// `V` must implement [`Clone`], as other threads may hold references to - /// the associated value. + /// Inserts a key-value pair into the map, returning a clone of the value + /// previously corresponding to the key. /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html + /// If the map did have this key present, both the key and value are + /// updated. #[inline] pub fn insert(&self, key: K, value: V) -> Option where @@ -423,16 +444,11 @@ impl HashMap { self.insert_entry_and(key, value, |_, v| v.clone()) } - /// Inserts a key-value pair, then returns a copy of the previous entry. - /// - /// If the key was not previously present in this hash map, [`None`] is - /// returned. - /// - /// `K` and `V` must implement [`Clone`], as other threads may hold - /// references to the entry. + /// Inserts a key-value pair into the map, returning a clone of the + /// key-value pair previously corresponding to the supplied key. /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html + /// If the map did have this key present, both the key and value are + /// updated. #[inline] pub fn insert_entry(&self, key: K, value: V) -> Option<(K, V)> where @@ -442,13 +458,12 @@ impl HashMap { self.insert_entry_and(key, value, |k, v| (k.clone(), v.clone())) } - /// Inserts a key-value pair, then invokes `with_previous_value` with the - /// value previously associated with `key`. - /// - /// If the key was not previously present in this hash map, [`None`] is - /// returned and `with_previous_value` is not invoked. + /// Inserts a key-value pair into the map, returning the result of invoking + /// a function with a reference to the value previously corresponding to the + /// key. /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// If the map did have this key present, both the key and value are + /// updated. #[inline] pub fn insert_and T, T>( &self, @@ -459,13 +474,12 @@ impl HashMap { self.insert_entry_and(key, value, move |_, v| with_previous_value(v)) } - /// Inserts a key-value pair, then invokes `with_previous_entry` with the - /// previous entry. + /// Inserts a key-value pair into the map, returning the result of invoking + /// a function with a reference to the key-value pair previously + /// corresponding to the supplied key. /// - /// If the key was not previously present in this hash map, [`None`] is - /// returned and `with_previous_entry` is not invoked. - /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// If the map did have this key present, both the key and value are + /// updated. #[inline] pub fn insert_entry_and T, T>( &self, @@ -486,16 +500,15 @@ impl HashMap { result } - /// If there is a value associated with `key`, remove and return a copy of - /// it. + /// Removes a key from the map, returning a clone of the value previously + /// corresponding to the key. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. `V` must implement [`Clone`], as other - /// threads may hold references to the associated value. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html #[inline] pub fn remove(&self, key: &Q) -> Option where @@ -505,16 +518,15 @@ impl HashMap { self.remove_entry_if_and(key, |_, _| true, |_, v| v.clone()) } - /// If there is a value associated with `key`, remove it and return a copy - /// of the previous entity. + /// Removes a key from the map, returning a clone of the key-value pair + /// previously corresponding to the key. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. `K` and `V` must implement [`Clone`], as other - /// threads may hold references to the entry. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html #[inline] pub fn remove_entry(&self, key: &Q) -> Option<(K, V)> where @@ -524,11 +536,12 @@ impl HashMap { self.remove_entry_if_and(key, |_, _| true, |k, v| (k.clone(), v.clone())) } - /// If there is a value associated with `key`, remove it and return the - /// result of invoking `with_previous_value` with that value. + /// Remove a key from the map, returning the result of invoking a function + /// with a reference to the value previously corresponding to the key. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html @@ -544,11 +557,13 @@ impl HashMap { self.remove_entry_if_and(key, |_, _| true, move |_, v| with_previous_value(v)) } - /// If there is a value associated with `key`, remove it and return the - /// result of invoking `with_previous_entry` with that entry. + /// Removes a key from the map, returning the result of invoking a function + /// with a reference to the key-value pair previously corresponding to the + /// key. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html @@ -564,21 +579,20 @@ impl HashMap { self.remove_entry_if_and(key, |_, _| true, with_previous_entry) } - /// If there is a value associated with `key` and `condition` returns true - /// when invoked with the current entry, remove and return a copy of its - /// value. + /// Removes a key from the map if a condition is met, returning a clone of + /// the value previously corresponding to the key. /// - /// `condition` may be invoked one or more times, even if no entry was - /// removed. + /// `condition` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. `K` and `V` must implement [`Clone`], as other - /// threads may hold references to the entry. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html - #[inline] + /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None pub fn remove_if bool>( &self, key: &Q, @@ -591,19 +605,20 @@ impl HashMap { self.remove_entry_if_and(key, condition, move |_, v| v.clone()) } - /// If there is a value associated with `key` and `condition` returns true - /// when invoked with the current entry, remove and return a copy of it. + /// Removes a key from the map if a condition is met, returning a clone of + /// the key-value pair previously corresponding to the key. /// - /// `condition` may be invoked one or more times, even if no entry was - /// removed. + /// `condition` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. `K` and `V` must implement [`Clone`], as other - /// threads may hold references to the entry. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html + /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn remove_entry_if bool>( &self, @@ -617,19 +632,20 @@ impl HashMap { self.remove_entry_if_and(key, condition, move |k, v| (k.clone(), v.clone())) } - /// If there is a value associated with `key` and `condition` returns true - /// when invoked with the current entry, remove it and return the result of - /// invoking `with_previous_value` with its value. + /// Remove a key from the map if a condition is met, returning the result of + /// invoking a function with a reference to the value previously + /// corresponding to the key. /// - /// `condition` may be invoked one or more times, even if no entry was - /// removed. If `condition` failed or there was no value associated with - /// `key`, `with_previous_entry` is not invoked and [`None`] is returned. + /// `condition` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html + /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn remove_if_and bool, G: FnOnce(&V) -> T, T>( @@ -644,19 +660,20 @@ impl HashMap { self.remove_entry_if_and(key, condition, move |_, v| with_previous_value(v)) } - /// If there is a value associated with `key` and `condition` returns true - /// when invoked with the current entry, remove it and return the result of - /// invoking `with_previous_entry` with it. + /// Removes a key from the map if a condition is met, returning the result + /// of invoking a function with a reference to the key-value pair previously + /// corresponding to the key. /// - /// `condition` may be invoked one or more times, even if no entry was - /// removed. If `condition` failed or there was no value associated with - /// `key`, `with_previous_entry` is not invoked and [`None`] is returned. + /// `condition` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. + /// The key may be any borrowed form of the map's key type, but + /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for + /// the key type. /// /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html + /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn remove_entry_if_and< @@ -683,18 +700,15 @@ impl HashMap { }) } - /// Insert a value if none is associated with `key`. Otherwise, replace the - /// value with the result of `on_modify` with the current entry as - /// arguments. Finally, return a copy of the previously associated value. - /// - /// If there is no value associated with `key`, [`None`] will be returned. - /// `on_modify` may be invoked multiple times, even if [`None`] is returned. + /// If no value corresponds to the key, insert a new key-value pair into + /// the map. Otherwise, modify the existing value and return a clone of the + /// value previously corresponding to the key. /// - /// `V` must implement [`Clone`], as other threads may hold references to - /// the associated value. + /// `on_modify` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// + /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html #[inline] pub fn insert_or_modify V>( &self, @@ -708,18 +722,15 @@ impl HashMap { self.insert_with_or_modify_entry_and(key, move || value, on_modify, |_, v| v.clone()) } - /// Insert a value if none is associated with `key`. Otherwise, replace the - /// value with the result of `on_modify` with the current entry as - /// arguments. Finally, return a copy of the previous entry. - /// - /// If there is no value associated with `key`, [`None`] will be returned. - /// `on_modify` may be invoked multiple times, even if [`None`] is returned. + /// If no value corresponds to the key, insert a new key-value pair into + /// the map. Otherwise, modify the existing value and return a clone of the + /// key-value pair previously corresponding to the key. /// - /// `K` and `V` must implement [`Clone`], as other threads may hold - /// references to the entry. + /// `on_modify` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// + /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html #[inline] pub fn insert_or_modify_entry V>( &self, @@ -739,22 +750,17 @@ impl HashMap { ) } - /// Insert the result of `on_insert` if no value is associated with `key`. - /// Otherwise, replace the value with the result of `on_modify` with the - /// current entry as arguments. Finally, return a copy of the previously - /// associated value. + /// If no value corresponds to the key, invoke a default function to insert + /// a new key-value pair into the map. Otherwise, modify the existing value + /// and return a clone of the value previously corresponding to the key. /// - /// If there is no value associated with `key`, `on_insert` will be invoked - /// and [`None`] will be returned. `on_modify` may be invoked multiple - /// times, even if [`None`] is returned. Similarly, `on_insert` may be - /// invoked if [`Some`] is returned. + /// `on_insert` may be invoked, even if [`None`] is returned. /// - /// `V` must implement [`Clone`], as other threads may hold references to - /// the associated value. + /// `on_modify` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn insert_with_or_modify V, G: FnMut(&K, &V) -> V>( &self, @@ -768,22 +774,18 @@ impl HashMap { self.insert_with_or_modify_entry_and(key, on_insert, on_modify, |_, v| v.clone()) } - /// Insert the result of `on_insert` if no value is associated with `key`. - /// Otherwise, replace the value with the result of `on_modify` with the - /// current entry as arguments. Finally, return a copy of the previous - /// entry. + /// If no value corresponds to the key, invoke a default function to insert + /// a new key-value pair into the map. Otherwise, modify the existing value + /// and return a clone of the key-value pair previously corresponding to the + /// key. /// - /// If there is no value associated with `key`, `on_insert` will be invoked - /// and [`None`] will be returned. `on_modify` may be invoked multiple - /// times, even if [`None`] is returned. Similarly, `on_insert` may be - /// invoked if [`Some`] is returned. + /// `on_insert` may be invoked, even if [`None`] is returned. /// - /// `K` and `V` must implement [`Clone`], as other threads may hold - /// references to the entry. + /// `on_modify` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn insert_with_or_modify_entry V, G: FnMut(&K, &V) -> V>( &self, @@ -800,15 +802,15 @@ impl HashMap { }) } - /// Insert a value if none is associated with `key`. Otherwise, replace the - /// value with the result of `on_modify` with the current entry as - /// arguments. Finally, return the result of invoking `with_old_value` with - /// the previously associated value. + /// If no value corresponds to the key, insert a new key-value pair into + /// the map. Otherwise, modify the existing value and return the result of + /// invoking a function with a reference to the value previously + /// corresponding to the key. /// - /// If there is no value associated with `key`, `with_old_value` will not be - /// invoked and [`None`] will be returned. `on_modify` may be invoked - /// multiple times, even if [`None`] is returned. + /// `on_modify` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// + /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn insert_or_modify_and V, G: FnOnce(&V) -> T, T>( @@ -826,15 +828,15 @@ impl HashMap { ) } - /// Insert a value if none is associated with `key`. Otherwise, replace the - /// value with the result of `on_modify` with the current entry as - /// arguments. Finally, return the result of invoking `with_old_entry` with - /// the previous entry. + /// If no value corresponds to the key, insert a new key-value pair into + /// the map. Otherwise, modify the existing value and return the result of + /// invoking a function with a reference to the key-value pair previously + /// corresponding to the supplied key. /// - /// If there is no value associated with `key`, `with_old_value` will not be - /// invoked and [`None`] will be returned. `on_modify` may be invoked - /// multiple times, even if [`None`] is returned. + /// `on_modify` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// + /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn insert_or_modify_entry_and V, G: FnOnce(&K, &V) -> T, T>( @@ -847,18 +849,18 @@ impl HashMap { self.insert_with_or_modify_entry_and(key, move || value, on_modify, with_old_entry) } - /// Insert the result of `on_insert` if no value is associated with `key`. - /// Otherwise, replace the value with the result of `on_modify` with the - /// current entry as arguments. Finally, return the result of invoking - /// `with_old_value` with the previously associated value. + /// If no value corresponds to the key, invoke a default function to insert + /// a new key-value pair into the map. Otherwise, modify the existing value + /// and return the result of invoking a function with a reference to the + /// value previously corresponding to the key. /// - /// If there is no value associated with `key`, `on_insert` will be invoked, - /// `with_old_value` will not be invoked, and [`None`] will be returned. - /// `on_modify` may be invoked multiple times, even if [`None`] is returned. - /// Similarly, `on_insert` may be invoked if [`Some`] is returned. + /// `on_insert` may be invoked, even if [`None`] is returned. + /// + /// `on_modify` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn insert_with_or_modify_and< F: FnOnce() -> V, @@ -877,18 +879,18 @@ impl HashMap { }) } - /// Insert the result of `on_insert` if no value is associated with `key`. - /// Otherwise, replace the value with the result of `on_modify` with the - /// current entry as arguments. Finally, return the result of invoking - /// `with_old_entry` with the previous entry. + /// If no value corresponds to the key, invoke a default function to insert + /// a new key-value pair into the map. Otherwise, modify the existing value + /// and return the result of invoking a function with a reference to the + /// key-value pair previously corresponding to the supplied key. /// - /// If there is no value associated with `key`, `on_insert` will be invoked, - /// `with_old_value` will not be invoked, and [`None`] will be returned. - /// `on_modify` may be invoked multiple times, even if [`None`] is returned. - /// Similarly, `on_insert` may be invoked if [`Some`] is returned. + /// `on_insert` may be invoked, even if [`None`] is returned. + /// + /// `on_modify` will be invoked at least once if [`Some`] is returned. It + /// may also be invoked one or more times if [`None`] is returned. /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None /// [`Some`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.Some + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None #[inline] pub fn insert_with_or_modify_entry_and< F: FnOnce() -> V, @@ -919,21 +921,8 @@ impl HashMap { result } - /// If there is a value associated with `key`, replace it with the result of - /// invoking `on_modify` using the current key and value, then return a copy - /// of the previously associated value. - /// - /// If there is no value associated with `key`, [`None`] will be returned. - /// `on_modify` may be invoked multiple times, even if [`None`] is returned. - /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. `V` must implement [`Clone`], as other - /// threads may hold references to the associated value. - /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None - /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html - /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html + /// Modifies the value corresponding to a key, returning a clone of the + /// value previously corresponding to that key. #[inline] pub fn modify V>(&self, key: K, on_modify: F) -> Option where @@ -942,21 +931,8 @@ impl HashMap { self.modify_entry_and(key, on_modify, |_, v| v.clone()) } - /// If there is a value associated with `key`, replace it with the result of - /// invoking `on_modify` using the current key and value, then return a copy - /// of the previously entry. - /// - /// If there is no value associated with `key`, [`None`] will be returned. - /// `on_modify` may be invoked multiple times, even if [`None`] is returned. - /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. `K` and `V` must implement [`Clone`], as other - /// threads may hold references to the entry. - /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None - /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html - /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html - /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html + /// Modifies the value corresponding to a key, returning a clone of the + /// key-value pair previously corresponding to that key. #[inline] pub fn modify_entry V>(&self, key: K, on_modify: F) -> Option<(K, V)> where @@ -966,21 +942,9 @@ impl HashMap { self.modify_entry_and(key, on_modify, |k, v| (k.clone(), v.clone())) } - /// If there is a value associated with `key`, replace it with the result of - /// invoking `on_modify` using the current key and value, then return the - /// result of invoking `with_old_value` with the previously associated - /// value. - /// - /// If there is no value associated with `key`, `with_old_value` will not be - /// invoked and [`None`] will be returned. `on_modify` may be invoked - /// multiple times, even if [`None`] is returned. - /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. - /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None - /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html - /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html + /// Modifies the value corresponding to a key, returning the result of + /// invoking a function with a reference to the value previously + /// corresponding to the key. #[inline] pub fn modify_and V, G: FnOnce(&V) -> T, T>( &self, @@ -991,20 +955,9 @@ impl HashMap { self.modify_entry_and(key, on_modify, move |_, v| with_old_value(v)) } - /// If there is a value associated with `key`, replace it with the result of - /// invoking `on_modify` using the current key and value, then return the - /// result of invoking `with_old_value` with the previous entry. - /// - /// If there is no value associated with `key`, `with_old_value` will not be - /// invoked and [`None`] will be returned. `on_modify` may be invoked - /// multiple times, even if [`None`] is returned. - /// - /// `Q` can be any borrowed form of `K`, but [`Hash`] and [`Eq`] on `Q` - /// *must* match that of `K`. - /// - /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None - /// [`Hash`]: https://doc.rust-lang.org/std/hash/trait.Hash.html - /// [`Eq`]: https://doc.rust-lang.org/std/cmp/trait.Eq.html + /// Modifies the value corresponding to a key, returning the result of + /// invoking a function with a reference to the key-value pair previously + /// corresponding to the supplied key. #[inline] pub fn modify_entry_and V, G: FnOnce(&K, &V) -> T, T>( &self, @@ -1019,6 +972,13 @@ impl HashMap { } } +#[cfg(feature = "num-cpus")] +impl Default for HashMap { + fn default() -> Self { + HashMap::with_num_segments_capacity_and_hasher(default_num_segments(), 0, S::default()) + } +} + impl Drop for HashMap { fn drop(&mut self) { let guard = unsafe { &crossbeam_epoch::unprotected() }; @@ -1088,6 +1048,11 @@ struct Segment { len: AtomicUsize, } +#[cfg(feature = "num-cpus")] +fn default_num_segments() -> usize { + num_cpus::get() * 2 +} + #[cfg(test)] mod tests { use crate::write_test_cases_for_me; diff --git a/src/test_util/tests.rs b/src/test_util/tests.rs index 04840d8..c0fd411 100644 --- a/src/test_util/tests.rs +++ b/src/test_util/tests.rs @@ -953,5 +953,39 @@ macro_rules! write_test_cases_for_me { $crate::test_util::run_deferred(); } + + #[test] + fn default() { + let map = $m::<_, _, $crate::map::DefaultHashBuilder>::default(); + + assert!(map.is_empty()); + assert_eq!(map.len(), 0); + + assert_eq!(map.insert("foo", 5), None); + assert_eq!(map.insert("bar", 10), None); + assert_eq!(map.insert("baz", 15), None); + assert_eq!(map.insert("qux", 20), None); + + assert!(!map.is_empty()); + assert_eq!(map.len(), 4); + + assert_eq!(map.insert("foo", 5), Some(5)); + assert_eq!(map.insert("bar", 10), Some(10)); + assert_eq!(map.insert("baz", 15), Some(15)); + assert_eq!(map.insert("qux", 20), Some(20)); + + assert!(!map.is_empty()); + assert_eq!(map.len(), 4); + + assert_eq!(map.remove("foo"), Some(5)); + assert_eq!(map.remove("bar"), Some(10)); + assert_eq!(map.remove("baz"), Some(15)); + assert_eq!(map.remove("qux"), Some(20)); + + assert!(map.is_empty()); + assert_eq!(map.len(), 0); + + $crate::test_util::run_deferred(); + } }; }