From 6dbf50c2aa49dd7314c039228e777b9aed5e435e Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 26 Jun 2020 14:48:38 -0600 Subject: [PATCH 1/5] Add crossbeam-skiplist crate-level docs. --- crossbeam-skiplist/src/base.rs | 2 +- crossbeam-skiplist/src/lib.rs | 174 ++++++++++++++++++++++++++++++++- 2 files changed, 174 insertions(+), 2 deletions(-) diff --git a/crossbeam-skiplist/src/base.rs b/crossbeam-skiplist/src/base.rs index 92eb2aaa4..ab629b0e6 100644 --- a/crossbeam-skiplist/src/base.rs +++ b/crossbeam-skiplist/src/base.rs @@ -356,7 +356,7 @@ impl SkipList { pub fn len(&self) -> usize { let len = self.hot_data.len.load(Ordering::Relaxed); - // Due to the relaxed memory ordering, the length counter may sometimes + // Due to the relaxed memory ordering, the length counter may sometimes // underflow and produce a very large value. We treat such values as 0. if len > isize::max_value() as usize { 0 diff --git a/crossbeam-skiplist/src/lib.rs b/crossbeam-skiplist/src/lib.rs index 9ca0c51cb..7201b8c9d 100644 --- a/crossbeam-skiplist/src/lib.rs +++ b/crossbeam-skiplist/src/lib.rs @@ -1,4 +1,176 @@ -//! TODO +//! Concurrent maps and sets based on [skip lists]. +//! +//! This crate provides the types [`SkipMap`] and [`SkipSet`]. +//! These data structures provide an interface similar to [`BTreeMap`] and [`BTreeSet`], +//! respectively, except they support safe concurrent access across +//! multiple threads. +//! +//! # Concurrent access +//! [`SkipMap`] and [`SkipSet`] implement `Send` and `Sync`, +//! so they can be shared across threads with ease. +//! +//! Methods which mutate the map, such as [`insert`], +//! take `&self` rather than `&mut self`. This allows +//! them to be invoked concurrently. +//! +//! ``` +//! use crossbeam_skiplist::SkipMap; +//! use crossbeam_utils::thread::scope; +//! +//! let person_ages = SkipMap::new(); +//! +//! scope(|s| { +//! // Insert entries into the map from multiple threads. +//! s.spawn(|_| { +//! person_ages.insert("Spike Garrett", 22); +//! person_ages.insert("Stan Hancock", 47); +//! person_ages.insert("Rea Bryan", 234); +//! +//! assert_eq!(person_ages.get("Spike Garrett").unwrap().value(), &22); +//! }); +//! s.spawn(|_| { +//! person_ages.insert("Bryon Conroy", 65); +//! person_ages.insert("Lauren Reilly", 2); +//! }); +//! }).unwrap(); +//! +//! assert!(person_ages.contains_key("Spike Garrett")); +//! person_ages.remove("Rea Bryan"); +//! assert!(!person_ages.contains_key("Rea Bryan")); +//! +//! ``` +//! +//! Concurrent access to skip lists is lock-free and sound. +//! Threads won't get blocked waiting for other threads to finish operating +//! on the map. +//! +//! Be warned that, because of this lock-freedom, it's easy to introduce +//! race conditions into your code. For example: +//! ```no_run +//! use crossbeam_skiplist::SkipSet; +//! use crossbeam_utils::thread::scope; +//! +//! let numbers = SkipSet::new(); +//! scope(|s| { +//! numbers.insert(5); +//! +//! // Spawn a thread which will remove 5 from the set. +//! s.spawn(|_| { +//! numbers.remove(&5); +//! }); +//! +//! // This check can fail! +//! // The othe thread may remove the value +//! // we perform this check. +//! assert!(numbers.contains(&5)); +//! }).unwrap(); +//! ``` +//! +//! In effect, a _single_ operation on the map, such as [`insert`], +//! operates atomically: race conditions are impossible. However, +//! concurrent calls to functions can become interleaved across +//! threads, introducing non-determinism into your code. +//! +//! To avoid this sort of race condition, never assume that a collection's +//! state will remain the same across multiple lines of code. For instance, +//! in the example above, the problem arises from the assumption that +//! the map won't be mutated between the calls to `insert` and `contains`. +//! In sequential code, this would be correct. But when multiple +//! threads are introduced, more care is needed. +//! +//! Note that race conditions do not violate Rust's memory safety rules. +//! A race between multiple threads can never cause memory errors or +//! segfaults. A race condition is a _logic error_ in its entirety. +//! +//! # Performance versus B-trees +//! In general, when you need concurrent writes +//! to an ordered collection, skip lists are a reasonable choice. +//! However, they can be substantially slower than B-trees +//! in some scenarios. +//! +//! The main benefit of a skip list over a `RwLock` +//! is that it allows concurrent writes to progress without +//! mutual exclusion. However, when the frequency +//! of writes is low, this benefit isn't as useful. +//! In these cases, a shared [`BTreeMap`] may be a faster option. +//! +//! These guidelines should be taken with a grain of salt—performance +//! in practice varies depending on your use case. +//! In the end, the best way to choose between [`BTreeMap`] and [`SkipMap`] +//! is to benchmark them in your own application. +//! +//! [`SkipMap`]: struct.SkipMap.html +//! [`SkipSet`]: struct.SkipSet.html +//! [`insert`]: struct.SkipSet.html#method.insert +//! [skip lists]: https://en.wikipedia.org/wiki/Skip_list +//! [`BTreeMap`]: https://doc.rust-lang.org/std/collections/struct.BTreeMap.html +//! [`BTreeSet`]: https://doc.rust-lang.org/std/collections/struct.BTreeSet.html +//! +//! # Examples +//! [`SkipMap`] basic usage: +//! ``` +//! use crossbeam_skiplist::SkipMap; +//! +//! // Note that the variable doesn't have to be mutable: +//! // SkipMap methods take &self to support concurrent access. +//! let movie_reviews = SkipMap::new(); +//! +//! // Insert some key-value pairs. +//! movie_reviews.insert("Office Space", "Deals with real issues in the workplace."); +//! movie_reviews.insert("Pulp Fiction", "Masterpiece."); +//! movie_reviews.insert("The Godfather", "Very enjoyable."); +//! movie_reviews.insert("The Blues Brothers", "Eye lyked it a lot."); +//! +//! // Get the value associated with a key. +//! // get() returns an Entry, which gives +//! // references to the key and value. +//! let pulp_fiction = movie_reviews.get("Pulp Fiction").unwrap(); +//! assert_eq!(*pulp_fiction.key(), "Pulp Fiction"); +//! assert_eq!(*pulp_fiction.value(), "Masterpiece."); +//! +//! // Remove a key-value pair. +//! movie_reviews.remove("The Blues Brothers"); +//! assert!(movie_reviews.get("The Blues Brothers").is_none()); +//! +//! // Iterate over the reviews. Since SkipMap +//! // is an ordered map, the iterator will yield +//! // keys in lexicographical order. +//! for entry in &movie_reviews { +//! let movie = entry.key(); +//! let review = entry.value(); +//! println!("{}: \"{}\"", movie, review); +//! } +//! ``` +//! +//! [`SkipSet`] basic usage: +//! ``` +//! use crossbeam_skiplist::SkipSet; +//! +//! let books = SkipSet::new(); +//! +//! // Add some books to the set. +//! books.insert("A Dance With Dragons"); +//! books.insert("To Kill a Mockingbird"); +//! books.insert("The Odyssey"); +//! books.insert("The Great Gatsby"); +//! +//! // Check for a specific one. +//! if !books.contains("The Winds of Winter") { +//! println!("We have {} books, but The Winds of Winter ain't one.", +//! books.len()); +//! } +//! +//! // Remove a book from the set. +//! books.remove("To Kill a Mockingbird"); +//! assert!(!books.contains("To Kill a Mockingbird")); +//! +//! // Iterate over the books in the set. +//! // Values are returned in lexicographical order. +//! for entry in &books { +//! let book = entry.value(); +//! println!("{}", book); +//! } +//! ``` #![doc(test( no_crate_inject, From b0045fab8943b07890e39ec0cff14db778f36dca Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 26 Jun 2020 16:04:10 -0600 Subject: [PATCH 2/5] Document mutable access and garbage collection scheme --- crossbeam-skiplist/src/lib.rs | 47 ++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/crossbeam-skiplist/src/lib.rs b/crossbeam-skiplist/src/lib.rs index 7201b8c9d..7189940a6 100644 --- a/crossbeam-skiplist/src/lib.rs +++ b/crossbeam-skiplist/src/lib.rs @@ -82,6 +82,47 @@ //! A race between multiple threads can never cause memory errors or //! segfaults. A race condition is a _logic error_ in its entirety. //! +//! # Mutable access to elements +//! [`SkipMap`] and [`SkipSet`] provide no way to retrieve a mutable reference +//! to a value. Since access methods can be called concurrently, providing +//! e.g. a `get_mut` function could cause data races. +//! +//! A solution to the above is to have the implementation wrap +//! each value in a lock. However, this has some repercussions: +//! * The map would no longer be lock-free, inhibiting scalability +//! and allowing for deadlocks. +//! * If a user of the map doesn't need mutable access, then they pay +//! the price of locks without actually needing them. +//! +//! Instead, the approach taken by this crate gives more control to the user. +//! If mutable access is needed, then you can use interior mutability, +//! such as [`RwLock`]: `SkipMap>`. +//! +//! # Garbage collection +//! A problem faced by many concurrent data structures +//! is choosing when to free unused memory. Care must be +//! taken to prevent use-after-frees and double-frees, both +//! of which cause undefined behvarior. +//! +//! Consider the following sequence of events operating on a [`SkipMap`]: +//! * Thread A calls [`get`] and holds a reference to a value in the map. +//! * Thread B removes that key from the map. +//! * Thread A now attempts to access the value. +//! +//! What happens here? If the map implementation frees the memory +//! belonging to a value when it is +//! removed, then a user-after-free occurs, resulting in memory corruption. +//! +//! To solve the above, this crate uses the _epoch-based memory reclamation_ mechanism +//! implemented in [`crossbeam-epoch`]. Simplified, a value removed from the map +//! is not freed until after all references to it have been dropped. This mechanism +//! is similar to the garbage collection found in some languages, such as Java, except +//! it operates solely on the values inside the map. +//! +//! This garbage collection scheme operates automatically; users don't have to worry about it. +//! However, keep in mind that holding [`Entry`] handles to entries in the map will prevent +//! that memory from being freed until at least after the handles are dropped. +//! //! # Performance versus B-trees //! In general, when you need concurrent writes //! to an ordered collection, skip lists are a reasonable choice. @@ -101,10 +142,14 @@ //! //! [`SkipMap`]: struct.SkipMap.html //! [`SkipSet`]: struct.SkipSet.html -//! [`insert`]: struct.SkipSet.html#method.insert +//! [`insert`]: struct.SkipMap.html#method.insert +//! [`get`]: struct.SkipMap.html#method.get +//! [`Entry`]: map/struct.Entry.html //! [skip lists]: https://en.wikipedia.org/wiki/Skip_list +//! [`crossbeam-epoch`]: https://docs.rs/crossbeam-epoch //! [`BTreeMap`]: https://doc.rust-lang.org/std/collections/struct.BTreeMap.html //! [`BTreeSet`]: https://doc.rust-lang.org/std/collections/struct.BTreeSet.html +//! [`RwLock`]: https://doc.rust-lang.org/std/sync/struct.RwLock.html //! //! # Examples //! [`SkipMap`] basic usage: From 387ce0d12924ce8045c38f22feb16fbc9a1d9cb4 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 26 Jun 2020 16:10:57 -0600 Subject: [PATCH 3/5] Minor doc adjustments + fix typos --- crossbeam-skiplist/src/lib.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crossbeam-skiplist/src/lib.rs b/crossbeam-skiplist/src/lib.rs index 7189940a6..a2647c05f 100644 --- a/crossbeam-skiplist/src/lib.rs +++ b/crossbeam-skiplist/src/lib.rs @@ -52,16 +52,17 @@ //! //! let numbers = SkipSet::new(); //! scope(|s| { -//! numbers.insert(5); -//! //! // Spawn a thread which will remove 5 from the set. //! s.spawn(|_| { //! numbers.remove(&5); //! }); +//! +//! // While the thread above is running, insert a value into the set. +//! numbers.insert(5); //! //! // This check can fail! -//! // The othe thread may remove the value -//! // we perform this check. +//! // The other thread may remove the value +//! // before we perform this check. //! assert!(numbers.contains(&5)); //! }).unwrap(); //! ``` @@ -69,7 +70,7 @@ //! In effect, a _single_ operation on the map, such as [`insert`], //! operates atomically: race conditions are impossible. However, //! concurrent calls to functions can become interleaved across -//! threads, introducing non-determinism into your code. +//! threads, introducing non-determinism. //! //! To avoid this sort of race condition, never assume that a collection's //! state will remain the same across multiple lines of code. For instance, @@ -102,7 +103,7 @@ //! A problem faced by many concurrent data structures //! is choosing when to free unused memory. Care must be //! taken to prevent use-after-frees and double-frees, both -//! of which cause undefined behvarior. +//! of which cause undefined behavior. //! //! Consider the following sequence of events operating on a [`SkipMap`]: //! * Thread A calls [`get`] and holds a reference to a value in the map. @@ -119,7 +120,7 @@ //! is similar to the garbage collection found in some languages, such as Java, except //! it operates solely on the values inside the map. //! -//! This garbage collection scheme operates automatically; users don't have to worry about it. +//! This garbage collection scheme functions automatically; users don't have to worry about it. //! However, keep in mind that holding [`Entry`] handles to entries in the map will prevent //! that memory from being freed until at least after the handles are dropped. //! From 013d52c9a8fef4cab00e7b1ea4b7781d2188c752 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 26 Jun 2020 16:21:14 -0600 Subject: [PATCH 4/5] Document alternatives for unordered maps/sets --- crossbeam-skiplist/src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crossbeam-skiplist/src/lib.rs b/crossbeam-skiplist/src/lib.rs index a2647c05f..c1907b755 100644 --- a/crossbeam-skiplist/src/lib.rs +++ b/crossbeam-skiplist/src/lib.rs @@ -141,6 +141,18 @@ //! In the end, the best way to choose between [`BTreeMap`] and [`SkipMap`] //! is to benchmark them in your own application. //! +//! # Alternatives +//! This crate implements _ordered_ maps and sets, akin to [`BTreeMap`] and [`BTreeSet`]. +//! In many situations, however, a defined order on elements is not required. For these +//! purposes, unordered maps will suffice. In addition, unordered maps +//! often have better performance characteristics than their ordered alternatives. +//! +//! Crossbeam [does not currently provide a concurrent unordered map](https://github.com/crossbeam-rs/rfcs/issues/32). +//! That said, here are some other crates which may suit you: +//! * [`DashMap`](https://docs.rs/dashmap) implements a novel concurrent hash map +//! with good performance characteristics. +//! * [`flurry`](https://docs.rs/flurry) is a Rust port of Java's `ConcurrentHashMap`. +//! //! [`SkipMap`]: struct.SkipMap.html //! [`SkipSet`]: struct.SkipSet.html //! [`insert`]: struct.SkipMap.html#method.insert From 06f2bac02954b9f1cc05a4d7142fabcd670fdf26 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 26 Jun 2020 17:04:42 -0600 Subject: [PATCH 5/5] Document SkipMap member functions --- crossbeam-skiplist/src/map.rs | 300 +++++++++++++++++++++++++++++++++- 1 file changed, 294 insertions(+), 6 deletions(-) diff --git a/crossbeam-skiplist/src/map.rs b/crossbeam-skiplist/src/map.rs index 55fba9df5..586412e06 100644 --- a/crossbeam-skiplist/src/map.rs +++ b/crossbeam-skiplist/src/map.rs @@ -1,4 +1,4 @@ -//! TODO: docs +//! An ordered map based on a lock-free skip list. See [`SkipMap`](struct.SkipMap.html). use std::borrow::Borrow; use std::fmt; @@ -10,7 +10,12 @@ use std::ptr; use crate::base::{self, try_pin_loop}; use crate::epoch; -/// A map based on a lock-free skip list. +/// An ordered map based on a lock-free skip list. +/// +/// This is an alternative to [`BTreeMap`] which supports +/// concurrent access across multiple threads. +/// +/// [`BTreeMap`]: https://doc.rust-lang.org/std/collections/struct.BTreeMap.html pub struct SkipMap { inner: base::SkipList, } @@ -24,6 +29,17 @@ impl SkipMap { } /// Returns `true` if the map is empty. + /// + /// # Example + /// ``` + /// use crossbeam_skiplist::SkipMap; + /// + /// let map: SkipMap<&str, &str> = SkipMap::new(); + /// assert!(map.is_empty()); + /// + /// map.insert("key", "value"); + /// assert!(!map.is_empty()); + /// ``` pub fn is_empty(&self) -> bool { self.inner.is_empty() } @@ -32,6 +48,21 @@ impl SkipMap { /// /// If the map is being concurrently modified, consider the returned number just an /// approximation without any guarantees. + /// + /// # Example + /// ``` + /// use crossbeam_skiplist::SkipMap; + /// + /// let map = SkipMap::new(); + /// map.insert(0, 1); + /// assert_eq!(map.len(), 1); + /// + /// for x in 1..=5 { + /// map.insert(x, x + 1); + /// } + /// + /// assert_eq!(map.len(), 6); + /// ``` pub fn len(&self) -> usize { self.inner.len() } @@ -42,18 +73,61 @@ where K: Ord, { /// Returns the entry with the smallest key. + /// + /// This function returns an [`Entry`] which + /// can be used to access the key's associated value. + /// + /// [`Entry`]: map/struct.Entry.html + /// + /// # Example + /// ``` + /// use crossbeam_skiplist::SkipMap; + /// + /// let numbers = SkipMap::new(); + /// numbers.insert(5, "five"); + /// numbers.insert(6, "six"); + /// + /// assert_eq!(*numbers.front().unwrap().value(), "five"); + /// ``` pub fn front(&self) -> Option> { let guard = &epoch::pin(); try_pin_loop(|| self.inner.front(guard)).map(Entry::new) } /// Returns the entry with the largest key. + /// + /// This function returns an [`Entry`] which + /// can be used to access the key's associated value. + /// + /// [`Entry`]: map/struct.Entry.html + /// + /// # Example + /// ``` + /// use crossbeam_skiplist::SkipMap; + /// + /// let numbers = SkipMap::new(); + /// numbers.insert(5, "five"); + /// numbers.insert(6, "six"); + /// + /// assert_eq!(*numbers.back().unwrap().value(), "six"); + /// ``` pub fn back(&self) -> Option> { let guard = &epoch::pin(); try_pin_loop(|| self.inner.back(guard)).map(Entry::new) } /// Returns `true` if the map contains a value for the specified key. + /// + /// # Example + /// ``` + /// use crossbeam_skiplist::SkipMap; + /// + /// let ages = SkipMap::new(); + /// ages.insert("Bill Gates", 64); + /// + /// assert!(ages.contains_key(&"Bill Gates")); + /// assert!(!ages.contains_key(&"Steve Jobs")); + /// ``` pub fn contains_key(&self, key: &Q) -> bool where K: Borrow, @@ -64,6 +138,22 @@ where } /// Returns an entry with the specified `key`. + /// + /// This function returns an [`Entry`] which + /// can be used to access the key's associated value. + /// + /// [`Entry`]: map/struct.Entry.html + /// + /// # Example + /// ``` + /// use crossbeam_skiplist::SkipMap; + /// + /// let numbers: SkipMap<&str, i32> = SkipMap::new(); + /// assert!(numbers.get("six").is_none()); + /// + /// numbers.insert("six", 6); + /// assert_eq!(*numbers.get("six").unwrap().value(), 6); + /// ``` pub fn get(&self, key: &Q) -> Option> where K: Borrow, @@ -76,6 +166,31 @@ where /// Returns an `Entry` pointing to the lowest element whose key is above /// the given bound. If no such element is found then `None` is /// returned. + /// + /// This function returns an [`Entry`] which + /// can be used to access the key's associated value. + /// + /// [`Entry`]: map/struct.Entry.html + /// + /// # Example + /// ``` + /// use crossbeam_skiplist::SkipMap; + /// use std::ops::Bound::*; + /// + /// let numbers = SkipMap::new(); + /// numbers.insert(6, "six"); + /// numbers.insert(7, "seven"); + /// numbers.insert(12, "twelve"); + /// + /// let greater_than_five = numbers.lower_bound(Excluded(&5)).unwrap(); + /// assert_eq!(*greater_than_five.value(), "six"); + /// + /// let greater_than_six = numbers.lower_bound(Excluded(&6)).unwrap(); + /// assert_eq!(*greater_than_six.value(), "seven"); + /// + /// let greater_than_thirteen = numbers.lower_bound(Excluded(&13)); + /// assert!(greater_than_thirteen.is_none()); + /// ``` pub fn lower_bound<'a, Q>(&'a self, bound: Bound<&Q>) -> Option> where K: Borrow, @@ -88,6 +203,28 @@ where /// Returns an `Entry` pointing to the highest element whose key is below /// the given bound. If no such element is found then `None` is /// returned. + /// + /// This function returns an [`Entry`] which + /// can be used to access the key's associated value. + /// + /// [`Entry`]: map/struct.Entry.html + /// + /// # Example + /// ``` + /// use crossbeam_skiplist::SkipMap; + /// use std::ops::Bound::*; + /// + /// let numbers = SkipMap::new(); + /// numbers.insert(6, "six"); + /// numbers.insert(7, "seven"); + /// numbers.insert(12, "twelve"); + /// + /// let less_than_eight = numbers.upper_bound(Excluded(&8)).unwrap(); + /// assert_eq!(*less_than_eight.value(), "seven"); + /// + /// let less_than_six = numbers.upper_bound(Excluded(&6)); + /// assert!(less_than_six.is_none()); + /// ``` pub fn upper_bound<'a, Q>(&'a self, bound: Bound<&Q>) -> Option> where K: Borrow, @@ -98,12 +235,53 @@ where } /// Finds an entry with the specified key, or inserts a new `key`-`value` pair if none exist. + //// + /// This function returns an [`Entry`] which + /// can be used to access the key's associated value. + /// + /// [`Entry`]: map/struct.Entry.html + /// + /// # Example + /// ``` + /// use crossbeam_skiplist::SkipMap; + /// + /// let ages = SkipMap::new(); + /// let gates_age = ages.get_or_insert("Bill Gates", 64); + /// assert_eq!(*gates_age.value(), 64); + /// + /// ages.insert("Steve Jobs", 65); + /// let jobs_age = ages.get_or_insert("Steve Jobs", -1); + /// assert_eq!(*jobs_age.value(), 65); + /// ``` pub fn get_or_insert(&self, key: K, value: V) -> Entry<'_, K, V> { let guard = &epoch::pin(); Entry::new(self.inner.get_or_insert(key, value, guard)) } - /// Returns an iterator over all entries in the map. + /// Returns an iterator over all entries in the map, + /// sorted by key. + /// + /// This iterator returns [`Entry`]s which + /// can be used to access keys and their associated values. + /// + /// [`Entry`]: map/struct.Entry.html + /// + /// # Examples + /// ``` + /// use crossbeam_skiplist::SkipMap; + /// + /// let numbers = SkipMap::new(); + /// numbers.insert(6, "six"); + /// numbers.insert(7, "seven"); + /// numbers.insert(12, "twelve"); + /// + /// // Print then numbers from least to greatest + /// for entry in numbers.iter() { + /// let number = entry.key(); + /// let number_str = entry.value(); + /// println!("{} is {}", number, number_str); + /// } + /// ``` pub fn iter(&self) -> Iter<'_, K, V> { Iter { inner: self.inner.ref_iter(), @@ -111,6 +289,28 @@ where } /// Returns an iterator over a subset of entries in the skip list. + /// + /// This iterator returns [`Entry`]s which + /// can be used to access keys and their associated values. + /// + /// [`Entry`]: map/struct.Entry.html + /// + /// # Example + /// ``` + /// use crossbeam_skiplist::SkipMap; + /// + /// let numbers = SkipMap::new(); + /// numbers.insert(6, "six"); + /// numbers.insert(7, "seven"); + /// numbers.insert(12, "twelve"); + /// + /// // Print all numbers in the map between 5 and 8. + /// for entry in numbers.range(5..=8) { + /// let number = entry.key(); + /// let number_str = entry.value(); + /// println!("{} is {}", number, number_str); + /// } + /// ``` pub fn range(&self, range: R) -> Range<'_, Q, R, K, V> where K: Borrow, @@ -132,12 +332,46 @@ where /// /// If there is an existing entry with this key, it will be removed before inserting the new /// one. + /// + /// This function returns an [`Entry`] which + /// can be used to access the inserted key's associated value. + /// + /// [`Entry`]: map/struct.Entry.html + /// + /// # Example + /// ``` + /// use crossbeam_skiplist::SkipMap; + /// + /// let map = SkipMap::new(); + /// map.insert("key", "value"); + /// + /// assert_eq!(*map.get("key").unwrap().value(), "value"); + /// ``` pub fn insert(&self, key: K, value: V) -> Entry<'_, K, V> { let guard = &epoch::pin(); Entry::new(self.inner.insert(key, value, guard)) } /// Removes an entry with the specified `key` from the map and returns it. + /// + /// The value will not actually be dropped until all references to it have gone + /// out of scope. + /// + /// This function returns an [`Entry`] which + /// can be used to access the removed key's associated value. + /// + /// [`Entry`]: map/struct.Entry.html + /// + /// # Example + /// ``` + /// use crossbeam_skiplist::SkipMap; + /// + /// let map: SkipMap<&str, &str> = SkipMap::new(); + /// assert!(map.remove("invalid key").is_none()); + /// + /// map.insert("key", "value"); + /// assert_eq!(*map.remove("key").unwrap().value(), "value"); + /// ``` pub fn remove(&self, key: &Q) -> Option> where K: Borrow, @@ -147,19 +381,73 @@ where self.inner.remove(key, guard).map(Entry::new) } - /// Removes an entry from the front of the map. + /// Removes the entry with the lowest key + /// from the map. Returns the removed entry. + /// + /// The value will not actually be dropped until all references to it have gone + /// out of scope. + /// + /// # Example + /// ``` + /// use crossbeam_skiplist::SkipMap; + /// + /// let numbers = SkipMap::new(); + /// numbers.insert(6, "six"); + /// numbers.insert(7, "seven"); + /// numbers.insert(12, "twelve"); + /// + /// assert_eq!(*numbers.pop_front().unwrap().value(), "six"); + /// assert_eq!(*numbers.pop_front().unwrap().value(), "seven"); + /// assert_eq!(*numbers.pop_front().unwrap().value(), "twelve"); + /// + /// // All entries have been removed now. + /// assert!(numbers.pop_front().is_none()); + /// ``` pub fn pop_front(&self) -> Option> { let guard = &epoch::pin(); self.inner.pop_front(guard).map(Entry::new) } - /// Removes an entry from the back of the map. + /// Removes the entry with the greatest key from the map. + /// Returns the removed entry. + /// + /// The value will not actually be dropped until all references to it have gone + /// out of scope. + /// + /// # Example + /// ``` + /// use crossbeam_skiplist::SkipMap; + /// + /// let numbers = SkipMap::new(); + /// numbers.insert(6, "six"); + /// numbers.insert(7, "seven"); + /// numbers.insert(12, "twelve"); + /// + /// assert_eq!(*numbers.pop_back().unwrap().value(), "twelve"); + /// assert_eq!(*numbers.pop_back().unwrap().value(), "seven"); + /// assert_eq!(*numbers.pop_back().unwrap().value(), "six"); + /// + /// // All entries have been removed now. + /// assert!(numbers.pop_front().is_none()); + /// ``` pub fn pop_back(&self) -> Option> { let guard = &epoch::pin(); self.inner.pop_back(guard).map(Entry::new) } - /// Iterates over the map and removes every entry. + /// Removes all entries from the map. + /// + /// # Example + /// ``` + /// use crossbeam_skiplist::SkipMap; + /// + /// let people = SkipMap::new(); + /// people.insert("Bill", "Gates"); + /// people.insert("Steve", "Jobs"); + /// + /// people.clear(); + /// assert!(people.is_empty()); + /// ``` pub fn clear(&self) { let guard = &mut epoch::pin(); self.inner.clear(guard);