Skip to content

Commit

Permalink
Implemented HashMap::try_insert (#74)
Browse files Browse the repository at this point in the history
If an entry already exists in the map for a given key, the existing
`HashMap::insert` replaces it. That is not always what you want. This
patch adds `HashMap::try_insert`, which returns an error if the key is
already present in the map.
  • Loading branch information
GarkGarcia authored and jonhoo committed Mar 24, 2020
1 parent cbb9ac5 commit d156eaf
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 11 deletions.
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ mod set;
/// Iterator types.
pub mod iter;

pub use map::HashMap;
pub use map::{HashMap, TryInsertError};
pub use map_ref::HashMapRef;
pub use set::HashSet;

Expand Down
96 changes: 88 additions & 8 deletions src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use crate::node::*;
use crate::raw::*;
use crossbeam_epoch::{self as epoch, Atomic, Guard, Owned, Shared};
use std::borrow::Borrow;
use std::fmt::{self, Debug, Formatter};
use std::error::Error;
use std::fmt::{self, Debug, Display, Formatter};
use std::hash::{BuildHasher, Hash, Hasher};
use std::iter::FromIterator;
use std::sync::{
Expand Down Expand Up @@ -143,8 +144,7 @@ enum PutResult<'a, T> {
new: &'a T,
},
Exists {
old: &'a T,
#[allow(dead_code)]
current: &'a T,
not_inserted: Box<T>,
},
}
Expand All @@ -154,7 +154,7 @@ impl<'a, T> PutResult<'a, T> {
match *self {
PutResult::Inserted { .. } => None,
PutResult::Replaced { old, .. } => Some(old),
PutResult::Exists { old, .. } => Some(old),
PutResult::Exists { current, .. } => Some(current),
}
}

Expand All @@ -168,6 +168,38 @@ impl<'a, T> PutResult<'a, T> {
}
}

/// The error type for the [`HashMap::try_insert`] method.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct TryInsertError<'a, V> {
/// A reference to the current value mapped to the key.
pub current: &'a V,
/// The value that [`HashMap::try_insert`] failed to insert.
pub not_inserted: V,
}

impl<'a, V> Display for TryInsertError<'a, V>
where
V: Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"Insert of \"{:?}\" failed as key was already present with value \"{:?}\"",
self.not_inserted, self.current
)
}
}

impl<'a, V> Error for TryInsertError<'a, V>
where
V: Debug,
{
#[inline]
fn source(&self) -> Option<&(dyn Error + 'static)> {
None
}
}

// ===
// the following methods only see Ks and Vs if there have been inserts.
// modifications to the map are all guarded by thread-safety bounds (Send + Sync + 'static).
Expand Down Expand Up @@ -1334,8 +1366,7 @@ where
/// If the map did not have this key present, [`None`] is returned.
///
/// If the map did have this key present, the value is updated, and the old
/// value is returned. The key is not updated, though; this matters for
/// types that can be `==` without being identical. See the [std-collections
/// value is returned. The key is left unchanged. See the [std-collections
/// documentation] for more.
///
/// [`None`]: std::option::Option::None
Expand All @@ -1362,6 +1393,55 @@ where
self.put(key, value, false, guard).before()
}

/// Inserts a key-value pair into the map unless the key already exists.
///
/// If the map does not contain the key, the key-value pair is inserted
/// and this method returns `Ok`.
///
/// If the map does contain the key, the map is left unchanged and this
/// method returns `Err`.
///
/// [std-collections documentation]: https://doc.rust-lang.org/std/collections/index.html#insert-and-complex-keys
///
/// # Examples
///
/// ```
/// use flurry::{HashMap, TryInsertError};
///
/// let map = HashMap::new();
/// let mref = map.pin();
///
/// mref.insert(37, "a");
/// assert_eq!(
/// mref.try_insert(37, "b"),
/// Err(TryInsertError { current: &"a", not_inserted: &"b"})
/// );
/// assert_eq!(mref.try_insert(42, "c"), Ok(&"c"));
/// assert_eq!(mref.get(&37), Some(&"a"));
/// assert_eq!(mref.get(&42), Some(&"c"));
/// ```
#[inline]
pub fn try_insert<'g>(
&'g self,
key: K,
value: V,
guard: &'g Guard,
) -> Result<&'g V, TryInsertError<'g, V>> {
match self.put(key, value, true, guard) {
PutResult::Exists {
current,
not_inserted,
} => Err(TryInsertError {
current,
not_inserted: *not_inserted,
}),
PutResult::Inserted { new } => Ok(new),
PutResult::Replaced { .. } => {
unreachable!("no_replacement cannot result in PutResult::Replaced")
}
}
}

fn put<'g>(
&'g self,
key: K,
Expand Down Expand Up @@ -1467,7 +1547,7 @@ where
// tree, and the node has been dropped, `value` is the last remaining pointer
// to the initial value.
return PutResult::Exists {
old: unsafe { v.deref() },
current: unsafe { v.deref() },
not_inserted: unsafe { value.into_owned().into_box() },
};
}
Expand Down Expand Up @@ -2506,7 +2586,7 @@ fn no_replacement_return_val() {
assert_eq!(
map.put(42, String::from("world"), true, &guard),
PutResult::Exists {
old: &String::from("hello"),
current: &String::from("hello"),
not_inserted: Box::new(String::from("world")),
}
);
Expand Down
13 changes: 11 additions & 2 deletions src/map_ref.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::iter::*;
use crate::HashMap;
use crate::{HashMap, TryInsertError};
use crossbeam_epoch::Guard;
use std::borrow::Borrow;
use std::fmt::{self, Debug, Formatter};
Expand Down Expand Up @@ -148,12 +148,21 @@ where
V: 'static + Sync + Send,
S: BuildHasher,
{
/// Maps `key` to `value` in this table.
/// Inserts a key-value pair into the map.
///
/// See also [`HashMap::insert`].
pub fn insert(&self, key: K, value: V) -> Option<&'_ V> {
self.map.insert(key, value, &self.guard)
}

/// Inserts a key-value pair into the map unless the key already exists.
///
/// See also [`HashMap::try_insert`].
#[inline]
pub fn try_insert(&self, key: K, value: V) -> Result<&'_ V, TryInsertError<'_, V>> {
self.map.try_insert(key, value, &self.guard)
}

/// If the value for the specified `key` is present, attempts to
/// compute a new mapping given the key and its current mapped value.
/// See also [`HashMap::compute_if_present`].
Expand Down

0 comments on commit d156eaf

Please sign in to comment.