diff --git a/src/rodeo.rs b/src/rodeo.rs index 5c32a42..fee73e0 100644 --- a/src/rodeo.rs +++ b/src/rodeo.rs @@ -291,6 +291,42 @@ where .expect("Failed to get or intern string") } + /// Get a boolean signifying whether the string is previously unseen and the key for it, interning it if it is + /// + /// # Panics + /// + /// Panics if the key's `try_from_usize` function fails. With the default keys, this means that + /// you've interned more strings than it can handle. (For [`Spur`] this means that `u32::MAX - 1` + /// unique strings were interned) + /// + /// # Example + /// + /// ```rust + /// use lasso::Rodeo; + /// + /// let mut rodeo = Rodeo::default(); + /// + /// // Interned the string + /// let (is_new, key) = rodeo.insert("Strings of things with wings and dings"); + /// assert!(is_new); + /// assert_eq!("Strings of things with wings and dings", rodeo.resolve(&key)); + /// + /// // No string was interned, as it was already contained + /// let (is_new, key) = rodeo.insert("Strings of things with wings and dings"); + /// assert!(!is_new); + /// assert_eq!("Strings of things with wings and dings", rodeo.resolve(&key)); + /// ``` + /// + /// [`Spur`]: crate::Spur + #[cfg_attr(feature = "inline-more", inline)] + pub fn insert(&mut self, val: T) -> (bool, K) + where + T: AsRef, + { + self.try_insert(val) + .expect("Failed to get or intern string") + } + /// Get the key for a string, interning it if it does not yet exist /// /// # Example @@ -311,6 +347,34 @@ where /// #[cfg_attr(feature = "inline-more", inline)] pub fn try_get_or_intern(&mut self, val: T) -> LassoResult + where + T: AsRef, + { + self.try_insert(val).map(|(_yes, key)| key) + } + + /// Get a boolean signifying whether the string is previously unseen and the key for it, interning it if it is + /// + /// # Example + /// + /// ```rust + /// use lasso::Rodeo; + /// + /// let mut rodeo = Rodeo::default(); + /// + /// // Interned the string + /// let (is_new, key) = rodeo.try_insert("Strings of things with wings and dings").unwrap(); + /// assert!(is_new); + /// assert_eq!("Strings of things with wings and dings", rodeo.resolve(&key)); + /// + /// // No string was interned, as it was already contained + /// let (is_new, key) = rodeo.try_insert("Strings of things with wings and dings").unwrap(); + /// assert!(!is_new); + /// assert_eq!("Strings of things with wings and dings", rodeo.resolve(&key)); + /// ``` + /// + #[cfg_attr(feature = "inline-more", inline)] + pub fn try_insert(&mut self, val: T) -> LassoResult<(bool, K)> where T: AsRef, { @@ -327,9 +391,9 @@ where let hash = hash_string(hasher, string_slice); // Get the map's entry that the string should occupy - let key = match get_string_entry_mut(map, strings, hash, string_slice) { + match get_string_entry_mut(map, strings, hash, string_slice) { // The string already exists, so return its key - RawEntryMut::Occupied(entry) => *entry.into_key(), + RawEntryMut::Occupied(entry) => Ok((false, *entry.into_key())), // The string does not yet exist, so insert it and create its key RawEntryMut::Vacant(entry) => { @@ -347,11 +411,9 @@ where // Insert the key with the hash of the string that it points to, reusing the hash we made earlier insert_string(entry, strings, hasher, hash, key); - key + Ok((true, key)) } - }; - - Ok(key) + } } /// Get the key for a static string, interning it if it does not yet exist @@ -386,6 +448,41 @@ where .expect("Failed to get or intern static string") } + /// Get a boolean signifying whether the static string is previously unseen and the key for it, interning it if it is + /// + /// This will not reallocate or copy the given string + /// + /// # Panics + /// + /// Panics if the key's `try_from_usize` function fails. With the default keys, this means that + /// you've interned more strings than it can handle. (For [`Spur`] this means that `u32::MAX - 1` + /// unique strings were interned) + /// + /// # Example + /// + /// ```rust + /// use lasso::Rodeo; + /// + /// let mut rodeo = Rodeo::default(); + /// + /// // Interned the string + /// let (is_new, key) = rodeo.insert_static("Strings of things with wings and dings"); + /// assert!(is_new); + /// assert_eq!("Strings of things with wings and dings", rodeo.resolve(&key)); + /// + /// // No string was interned, as it was already contained + /// let (is_new, key) = rodeo.insert_static("Strings of things with wings and dings"); + /// assert!(!is_new); + /// assert_eq!("Strings of things with wings and dings", rodeo.resolve(&key)); + /// + /// ``` + /// + #[cfg_attr(feature = "inline-more", inline)] + pub fn insert_static(&mut self, string: &'static str) -> (bool, K) { + self.try_insert_static(string) + .expect("Failed to get or intern static string") + } + /// Get the key for a static string, interning it if it does not yet exist /// /// This will not reallocate or copy the given string @@ -408,6 +505,33 @@ where /// #[cfg_attr(feature = "inline-more", inline)] pub fn try_get_or_intern_static(&mut self, string: &'static str) -> LassoResult { + self.try_insert_static(string).map(|(_yes, key)| key) + } + + /// Get a boolean signifying whether the static string is previously unseen and the key for it, interning it if it is + /// + /// This will not reallocate or copy the given string + /// + /// # Example + /// + /// ```rust + /// use lasso::Rodeo; + /// + /// let mut rodeo = Rodeo::default(); + /// + /// // Interned the string + /// let (is_new, key) = rodeo.try_insert_static("Strings of things with wings and dings").unwrap(); + /// assert!(is_new); + /// assert_eq!("Strings of things with wings and dings", rodeo.resolve(&key)); + /// + /// // No string was interned, as it was already contained + /// let (is_new, key) = rodeo.try_insert_static("Strings of things with wings and dings").unwrap(); + /// assert!(!is_new); + /// assert_eq!("Strings of things with wings and dings", rodeo.resolve(&key)); + /// ``` + /// + #[cfg_attr(feature = "inline-more", inline)] + pub fn try_insert_static(&mut self, string: &'static str) -> LassoResult<(bool, K)> { let Self { map, hasher, @@ -419,9 +543,9 @@ where let hash = hash_string(hasher, string); // Get the map's entry that the string should occupy - let key = match get_string_entry_mut(map, strings, hash, string) { + match get_string_entry_mut(map, strings, hash, string) { // The string already exists, so return its key - RawEntryMut::Occupied(entry) => *entry.into_key(), + RawEntryMut::Occupied(entry) => Ok((false, *entry.into_key())), // The string does not yet exist, so insert it and create its key RawEntryMut::Vacant(entry) => { @@ -435,11 +559,9 @@ where // Insert the key with the hash of the string that it points to, reusing the hash we made earlier insert_string(entry, strings, hasher, hash, key); - key + Ok((true, key)) } - }; - - Ok(key) + } } /// Get the key value of a string, returning `None` if it doesn't exist @@ -761,6 +883,13 @@ impl Rodeo { Strings::from_rodeo(self) } + /// Returns a reference to the slice of interned strings + #[cfg_attr(feature = "inline-more", inline)] + pub fn as_str(&self) -> &[&str] { + // Safety: we are shortening the &str lifetime from static to self, so they won't outlive the arens + &self.strings + } + /// Set the `Rodeo`'s maximum memory usage while in-flight /// /// Note that setting the maximum memory usage to below the currently allocated diff --git a/src/threaded_rodeo.rs b/src/threaded_rodeo.rs index 51a27d4..f7f5b36 100644 --- a/src/threaded_rodeo.rs +++ b/src/threaded_rodeo.rs @@ -13,7 +13,7 @@ use core::{ ops::Index, sync::atomic::{AtomicUsize, Ordering}, }; -use dashmap::{DashMap, mapref::entry::Entry, SharedValue}; +use dashmap::{mapref::entry::Entry, DashMap, SharedValue}; use hashbrown::{hash_map::RawEntryMut, HashMap}; macro_rules! index_unchecked_mut { @@ -290,6 +290,41 @@ where .expect("Failed to get or intern string") } + /// Get a boolean signifying whether the string is previously unseen and the key for it, interning it if it is + /// + /// # Panics + /// + /// Panics if the key's `try_from_usize` function fails. With the default keys, this means that + /// you've interned more strings than it can handle. (For [`Spur`] this means that `u32::MAX - 1` + /// unique strings were interned) + /// + /// # Example + /// + /// ```rust + /// use lasso::ThreadedRodeo; + /// + /// let rodeo = ThreadedRodeo::default(); + /// + /// // Interned the string + /// let (is_new, key) = rodeo.insert("Strings of things with wings and dings"); + /// assert!(is_new); + /// assert_eq!("Strings of things with wings and dings", rodeo.resolve(&key)); + /// + /// // No string was interned, as it was already contained + /// let (is_new, key) = rodeo.insert("Strings of things with wings and dings"); + /// assert!(!is_new); + /// assert_eq!("Strings of things with wings and dings", rodeo.resolve(&key)); + /// ``` + /// + #[cfg_attr(feature = "inline-more", inline)] + pub fn insert(&self, val: T) -> (bool, K) + where + T: AsRef, + { + self.try_insert(val) + .expect("Failed to get or intern string") + } + /// Get the key for a string, interning it if it does not yet exist /// /// # Example @@ -310,39 +345,65 @@ where /// #[cfg_attr(feature = "inline-more", inline)] pub fn try_get_or_intern(&self, val: T) -> LassoResult + where + T: AsRef, + { + self.try_insert(val).map(|(_yes, key)| key) + } + + /// Get a boolean signifying whether the string is previously unseen and the key for it, interning it if it is + /// + /// # Example + /// + /// ```rust + /// use lasso::ThreadedRodeo; + /// + /// let rodeo = ThreadedRodeo::default(); + /// + /// // Interned the string + /// let (is_new, key) = rodeo.insert("Strings of things with wings and dings"); + /// assert!(is_new); + /// assert_eq!("Strings of things with wings and dings", rodeo.resolve(&key)); + /// + /// // No string was interned, as it was already contained + /// let (is_new, key) = rodeo.insert("Strings of things with wings and dings"); + /// assert!(!is_new); + /// assert_eq!("Strings of things with wings and dings", rodeo.resolve(&key)); + /// ``` + /// + #[cfg_attr(feature = "inline-more", inline)] + pub fn try_insert(&self, val: T) -> LassoResult<(bool, K)> where T: AsRef, { let string_slice = val.as_ref(); if let Some(key) = self.map.get(string_slice) { - Ok(*key) + Ok((false, *key)) } else { // Determine which shard will have our `string_slice` key. let shard_key = self.map.determine_shard(self.map.hash_usize(&string_slice)); // Grab the shard and a write lock on it. let mut shard = self.map.shards().get(shard_key).unwrap().write(); - // Try getting the value for the `string_slice` key. If we get `Some`, nothing to do. - // Just return the value, which is the key go to use to resolve the string. If we + // Try getting the value for the `string_slice` key. If we get `Some`, nothing to do. + // Just return the value, which is the key go to use to resolve the string. If we // get `None`, an entry for the string doesn't exist yet. Store string in the arena, // update the maps accordingly, and return the key. - let key = match shard.get(string_slice) { - Some(v) => *v.get(), + match shard.get(string_slice) { + Some(v) => Ok((false, *v.get())), None => { // Safety: The drop impl removes all references before the arena is dropped let string: &'static str = unsafe { self.arena.store_str(string_slice)? }; - + let key = K::try_from_usize(self.key.fetch_add(1, Ordering::SeqCst)) .ok_or_else(|| LassoError::new(LassoErrorKind::KeySpaceExhaustion))?; - + self.strings.insert(key, string); shard.insert(string, SharedValue::new(key)); - key + Ok((true, key)) } - }; - - Ok(key) + } } } @@ -378,6 +439,40 @@ where .expect("Failed to get or intern static string") } + /// Get a boolean signifying whether the static string is previously unseen and the key for it, interning it if it is + /// + /// This will not reallocate or copy the given string but will instead just store it + /// + /// # Panics + /// + /// Panics if the key's `try_from_usize` function fails. With the default keys, this means that + /// you've interned more strings than it can handle. (For [`Spur`] this means that `u32::MAX - 1` + /// unique strings were interned) + /// + /// # Example + /// + /// ```rust + /// use lasso::ThreadedRodeo; + /// + /// let mut rodeo = ThreadedRodeo::default(); + /// + /// // Interned the string + /// let (is_new, key) = rodeo.insert_static("Strings of things with wings and dings"); + /// assert!(is_new); + /// assert_eq!("Strings of things with wings and dings", rodeo.resolve(&key)); + /// + /// // No string was interned, as it was already contained + /// let (is_new, key) = rodeo.insert_static("Strings of things with wings and dings"); + /// assert!(!is_new); + /// assert_eq!("Strings of things with wings and dings", rodeo.resolve(&key)); + /// ``` + /// + #[cfg_attr(feature = "inline-more", inline)] + pub fn insert_static(&self, string: &'static str) -> (bool, K) { + self.try_insert_static(string) + .expect("Failed to get or intern static string") + } + /// Get the key for a static string, interning it if it does not yet exist /// /// This will not reallocate and copy the given string but will instead just store it @@ -400,14 +495,38 @@ where /// #[cfg_attr(feature = "inline-more", inline)] pub fn try_get_or_intern_static(&self, string: &'static str) -> LassoResult { + self.try_insert_static(string).map(|(_yes, key)| key) + } + + /// Get a boolean signifying whether the static string is previously unseen and the key for it, interning it if it is + /// + /// This will not reallocate and copy the given string but will instead just store it + /// + /// # Example + /// + /// ```rust + /// use lasso::ThreadedRodeo; + /// + /// let mut rodeo = ThreadedRodeo::default(); + /// + /// // Interned the string + /// let (is_new, key) = rodeo.try_insert_static("Strings of things with wings and dings").unwrap(); + /// assert!(is_new); + /// assert_eq!("Strings of things with wings and dings", rodeo.resolve(&key)); + /// + /// // No string was interned, as it was already contained + /// let (is_new, key) = rodeo.try_insert_static("Strings of things with wings and dings").unwrap(); + /// assert!(!is_new); + /// assert_eq!("Strings of things with wings and dings", rodeo.resolve(&key)); + /// ``` + /// + #[cfg_attr(feature = "inline-more", inline)] + pub fn try_insert_static(&self, string: &'static str) -> LassoResult<(bool, K)> { if let Some(key) = self.map.get(string) { - Ok(*key) + Ok((false, *key)) } else { - let key = match self.map.entry(string) { - Entry::Occupied(o) => { - *o.get() - } + Entry::Occupied(o) => *o.get(), Entry::Vacant(v) => { let key = K::try_from_usize(self.key.fetch_add(1, Ordering::SeqCst)) .ok_or_else(|| LassoError::new(LassoErrorKind::KeySpaceExhaustion))?; @@ -418,7 +537,7 @@ where } }; - Ok(key) + Ok((true, key)) } }