Skip to content

Commit 232caad

Browse files
authored
Rollup merge of #82764 - m-ou-se:map-try-insert, r=Amanieu
Add {BTreeMap,HashMap}::try_insert `{BTreeMap,HashMap}::insert(key, new_val)` returns `Some(old_val)` if the key was already in the map. It's often useful to assert no duplicate values are inserted. We experimented with `map.insert(key, val).unwrap_none()` (#62633), but decided that that's not the kind of method we'd like to have on `Option`s. `insert` always succeeds because it replaces the old value if it exists. One could argue that `insert()` is never the right method for panicking on duplicates, since already handles that case by replacing the value, only allowing you to panic after that already happened. This PR adds a `try_insert` method that instead returns a `Result::Err` when the key already exists. This error contains both the `OccupiedEntry` and the value that was supposed to be inserted. This means that unwrapping that result gives more context: ```rust map.insert(10, "world").unwrap_none(); // thread 'main' panicked at 'called `Option::unwrap_none()` on a `Some` value: "hello"', src/main.rs:8:29 ``` ```rust map.try_insert(10, "world").unwrap(); // thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: // OccupiedError { key: 10, old_value: "hello", new_value: "world" }', src/main.rs:6:33 ``` It also allows handling the failure in any other way, as you have full access to the `OccupiedEntry` and the value. `try_insert` returns a reference to the value in case of success, making it an alternative to `.entry(key).or_insert(value)`. r? ```@Amanieu``` Fixes rust-lang/rfcs#3092
2 parents 68f2934 + eddd4f0 commit 232caad

File tree

5 files changed

+157
-1
lines changed

5 files changed

+157
-1
lines changed

library/alloc/src/collections/btree/map.rs

+35-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use super::node::{self, marker, ForceResult::*, Handle, NodeRef, Root};
1414
use super::search::SearchResult::*;
1515

1616
mod entry;
17-
pub use entry::{Entry, OccupiedEntry, VacantEntry};
17+
pub use entry::{Entry, OccupiedEntry, OccupiedError, VacantEntry};
1818
use Entry::*;
1919

2020
/// Minimum number of elements in nodes that are not a root.
@@ -836,6 +836,40 @@ impl<K, V> BTreeMap<K, V> {
836836
}
837837
}
838838

839+
/// Tries to insert a key-value pair into the map, and returns
840+
/// a mutable reference to the value in the entry.
841+
///
842+
/// If the map already had this key present, nothing is updated, and
843+
/// an error containing the occupied entry and the value is returned.
844+
///
845+
/// # Examples
846+
///
847+
/// Basic usage:
848+
///
849+
/// ```
850+
/// #![feature(map_try_insert)]
851+
///
852+
/// use std::collections::BTreeMap;
853+
///
854+
/// let mut map = BTreeMap::new();
855+
/// assert_eq!(map.try_insert(37, "a").unwrap(), &"a");
856+
///
857+
/// let err = map.try_insert(37, "b").unwrap_err();
858+
/// assert_eq!(err.entry.key(), &37);
859+
/// assert_eq!(err.entry.get(), &"a");
860+
/// assert_eq!(err.value, "b");
861+
/// ```
862+
#[unstable(feature = "map_try_insert", issue = "82766")]
863+
pub fn try_insert(&mut self, key: K, value: V) -> Result<&mut V, OccupiedError<'_, K, V>>
864+
where
865+
K: Ord,
866+
{
867+
match self.entry(key) {
868+
Occupied(entry) => Err(OccupiedError { entry, value }),
869+
Vacant(entry) => Ok(entry.insert(value)),
870+
}
871+
}
872+
839873
/// Removes a key from the map, returning the value at the key if the key
840874
/// was previously in the map.
841875
///

library/alloc/src/collections/btree/map/entry.rs

+35
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,41 @@ impl<K: Debug + Ord, V: Debug> Debug for OccupiedEntry<'_, K, V> {
7171
}
7272
}
7373

74+
/// The error returned by [`try_insert`](BTreeMap::try_insert) when the key already exists.
75+
///
76+
/// Contains the occupied entry, and the value that was not inserted.
77+
#[unstable(feature = "map_try_insert", issue = "82766")]
78+
pub struct OccupiedError<'a, K: 'a, V: 'a> {
79+
/// The entry in the map that was already occupied.
80+
pub entry: OccupiedEntry<'a, K, V>,
81+
/// The value which was not inserted, because the entry was already occupied.
82+
pub value: V,
83+
}
84+
85+
#[unstable(feature = "map_try_insert", issue = "82766")]
86+
impl<K: Debug + Ord, V: Debug> Debug for OccupiedError<'_, K, V> {
87+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88+
f.debug_struct("OccupiedError")
89+
.field("key", self.entry.key())
90+
.field("old_value", self.entry.get())
91+
.field("new_value", &self.value)
92+
.finish()
93+
}
94+
}
95+
96+
#[unstable(feature = "map_try_insert", issue = "82766")]
97+
impl<'a, K: Debug + Ord, V: Debug> fmt::Display for OccupiedError<'a, K, V> {
98+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99+
write!(
100+
f,
101+
"failed to insert {:?}, key {:?} already exists with value {:?}",
102+
self.value,
103+
self.entry.key(),
104+
self.entry.get(),
105+
)
106+
}
107+
}
108+
74109
impl<'a, K: Ord, V> Entry<'a, K, V> {
75110
/// Ensures a value is in the entry by inserting the default if empty, and returns
76111
/// a mutable reference to the value in the entry.

library/std/src/collections/hash/map.rs

+68
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// ignore-tidy-filelength
2+
13
#[cfg(test)]
24
mod tests;
35

@@ -842,6 +844,37 @@ where
842844
self.base.insert(k, v)
843845
}
844846

847+
/// Tries to insert a key-value pair into the map, and returns
848+
/// a mutable reference to the value in the entry.
849+
///
850+
/// If the map already had this key present, nothing is updated, and
851+
/// an error containing the occupied entry and the value is returned.
852+
///
853+
/// # Examples
854+
///
855+
/// Basic usage:
856+
///
857+
/// ```
858+
/// #![feature(map_try_insert)]
859+
///
860+
/// use std::collections::HashMap;
861+
///
862+
/// let mut map = HashMap::new();
863+
/// assert_eq!(map.try_insert(37, "a").unwrap(), &"a");
864+
///
865+
/// let err = map.try_insert(37, "b").unwrap_err();
866+
/// assert_eq!(err.entry.key(), &37);
867+
/// assert_eq!(err.entry.get(), &"a");
868+
/// assert_eq!(err.value, "b");
869+
/// ```
870+
#[unstable(feature = "map_try_insert", issue = "82766")]
871+
pub fn try_insert(&mut self, key: K, value: V) -> Result<&mut V, OccupiedError<'_, K, V>> {
872+
match self.entry(key) {
873+
Occupied(entry) => Err(OccupiedError { entry, value }),
874+
Vacant(entry) => Ok(entry.insert(value)),
875+
}
876+
}
877+
845878
/// Removes a key from the map, returning the value at the key if the key
846879
/// was previously in the map.
847880
///
@@ -1851,6 +1884,41 @@ impl<K: Debug, V> Debug for VacantEntry<'_, K, V> {
18511884
}
18521885
}
18531886

1887+
/// The error returned by [`try_insert`](HashMap::try_insert) when the key already exists.
1888+
///
1889+
/// Contains the occupied entry, and the value that was not inserted.
1890+
#[unstable(feature = "map_try_insert", issue = "82766")]
1891+
pub struct OccupiedError<'a, K: 'a, V: 'a> {
1892+
/// The entry in the map that was already occupied.
1893+
pub entry: OccupiedEntry<'a, K, V>,
1894+
/// The value which was not inserted, because the entry was already occupied.
1895+
pub value: V,
1896+
}
1897+
1898+
#[unstable(feature = "map_try_insert", issue = "82766")]
1899+
impl<K: Debug, V: Debug> Debug for OccupiedError<'_, K, V> {
1900+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1901+
f.debug_struct("OccupiedError")
1902+
.field("key", self.entry.key())
1903+
.field("old_value", self.entry.get())
1904+
.field("new_value", &self.value)
1905+
.finish()
1906+
}
1907+
}
1908+
1909+
#[unstable(feature = "map_try_insert", issue = "82766")]
1910+
impl<'a, K: Debug, V: Debug> fmt::Display for OccupiedError<'a, K, V> {
1911+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1912+
write!(
1913+
f,
1914+
"failed to insert {:?}, key {:?} already exists with value {:?}",
1915+
self.value,
1916+
self.entry.key(),
1917+
self.entry.get(),
1918+
)
1919+
}
1920+
}
1921+
18541922
#[stable(feature = "rust1", since = "1.0.0")]
18551923
impl<'a, K, V, S> IntoIterator for &'a HashMap<K, V, S> {
18561924
type Item = (&'a K, &'a V);

library/std/src/error.rs

+18
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,24 @@ impl Error for char::DecodeUtf16Error {
470470
}
471471
}
472472

473+
#[unstable(feature = "map_try_insert", issue = "82766")]
474+
impl<'a, K: Debug + Ord, V: Debug> Error
475+
for crate::collections::btree_map::OccupiedError<'a, K, V>
476+
{
477+
#[allow(deprecated)]
478+
fn description(&self) -> &str {
479+
"key already exists"
480+
}
481+
}
482+
483+
#[unstable(feature = "map_try_insert", issue = "82766")]
484+
impl<'a, K: Debug, V: Debug> Error for crate::collections::hash_map::OccupiedError<'a, K, V> {
485+
#[allow(deprecated)]
486+
fn description(&self) -> &str {
487+
"key already exists"
488+
}
489+
}
490+
473491
#[stable(feature = "box_error", since = "1.8.0")]
474492
impl<T: Error> Error for Box<T> {
475493
#[allow(deprecated, deprecated_in_future)]

library/std/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@
281281
#![feature(linkage)]
282282
#![feature(llvm_asm)]
283283
#![feature(log_syntax)]
284+
#![feature(map_try_insert)]
284285
#![feature(maybe_uninit_extra)]
285286
#![feature(maybe_uninit_ref)]
286287
#![feature(maybe_uninit_slice)]

0 commit comments

Comments
 (0)