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()` (https://github.com/rust-lang/rust/issues/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 https://github.com/rust-lang/rfcs/issues/3092
This commit is contained in:
commit
232caad395
5 changed files with 157 additions and 1 deletions
|
@ -14,7 +14,7 @@ use super::node::{self, marker, ForceResult::*, Handle, NodeRef, Root};
|
||||||
use super::search::SearchResult::*;
|
use super::search::SearchResult::*;
|
||||||
|
|
||||||
mod entry;
|
mod entry;
|
||||||
pub use entry::{Entry, OccupiedEntry, VacantEntry};
|
pub use entry::{Entry, OccupiedEntry, OccupiedError, VacantEntry};
|
||||||
use Entry::*;
|
use Entry::*;
|
||||||
|
|
||||||
/// Minimum number of elements in nodes that are not a root.
|
/// Minimum number of elements in nodes that are not a root.
|
||||||
|
@ -836,6 +836,40 @@ impl<K, V> BTreeMap<K, V> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tries to insert a key-value pair into the map, and returns
|
||||||
|
/// a mutable reference to the value in the entry.
|
||||||
|
///
|
||||||
|
/// If the map already had this key present, nothing is updated, and
|
||||||
|
/// an error containing the occupied entry and the value is returned.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Basic usage:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// #![feature(map_try_insert)]
|
||||||
|
///
|
||||||
|
/// use std::collections::BTreeMap;
|
||||||
|
///
|
||||||
|
/// let mut map = BTreeMap::new();
|
||||||
|
/// assert_eq!(map.try_insert(37, "a").unwrap(), &"a");
|
||||||
|
///
|
||||||
|
/// let err = map.try_insert(37, "b").unwrap_err();
|
||||||
|
/// assert_eq!(err.entry.key(), &37);
|
||||||
|
/// assert_eq!(err.entry.get(), &"a");
|
||||||
|
/// assert_eq!(err.value, "b");
|
||||||
|
/// ```
|
||||||
|
#[unstable(feature = "map_try_insert", issue = "82766")]
|
||||||
|
pub fn try_insert(&mut self, key: K, value: V) -> Result<&mut V, OccupiedError<'_, K, V>>
|
||||||
|
where
|
||||||
|
K: Ord,
|
||||||
|
{
|
||||||
|
match self.entry(key) {
|
||||||
|
Occupied(entry) => Err(OccupiedError { entry, value }),
|
||||||
|
Vacant(entry) => Ok(entry.insert(value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Removes a key from the map, returning the value at the key if the key
|
/// Removes a key from the map, returning the value at the key if the key
|
||||||
/// was previously in the map.
|
/// was previously in the map.
|
||||||
///
|
///
|
||||||
|
|
|
@ -71,6 +71,41 @@ impl<K: Debug + Ord, V: Debug> Debug for OccupiedEntry<'_, K, V> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The error returned by [`try_insert`](BTreeMap::try_insert) when the key already exists.
|
||||||
|
///
|
||||||
|
/// Contains the occupied entry, and the value that was not inserted.
|
||||||
|
#[unstable(feature = "map_try_insert", issue = "82766")]
|
||||||
|
pub struct OccupiedError<'a, K: 'a, V: 'a> {
|
||||||
|
/// The entry in the map that was already occupied.
|
||||||
|
pub entry: OccupiedEntry<'a, K, V>,
|
||||||
|
/// The value which was not inserted, because the entry was already occupied.
|
||||||
|
pub value: V,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unstable(feature = "map_try_insert", issue = "82766")]
|
||||||
|
impl<K: Debug + Ord, V: Debug> Debug for OccupiedError<'_, K, V> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("OccupiedError")
|
||||||
|
.field("key", self.entry.key())
|
||||||
|
.field("old_value", self.entry.get())
|
||||||
|
.field("new_value", &self.value)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unstable(feature = "map_try_insert", issue = "82766")]
|
||||||
|
impl<'a, K: Debug + Ord, V: Debug> fmt::Display for OccupiedError<'a, K, V> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"failed to insert {:?}, key {:?} already exists with value {:?}",
|
||||||
|
self.value,
|
||||||
|
self.entry.key(),
|
||||||
|
self.entry.get(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, K: Ord, V> Entry<'a, K, V> {
|
impl<'a, K: Ord, V> Entry<'a, K, V> {
|
||||||
/// Ensures a value is in the entry by inserting the default if empty, and returns
|
/// Ensures a value is in the entry by inserting the default if empty, and returns
|
||||||
/// a mutable reference to the value in the entry.
|
/// a mutable reference to the value in the entry.
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// ignore-tidy-filelength
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
@ -842,6 +844,37 @@ where
|
||||||
self.base.insert(k, v)
|
self.base.insert(k, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tries to insert a key-value pair into the map, and returns
|
||||||
|
/// a mutable reference to the value in the entry.
|
||||||
|
///
|
||||||
|
/// If the map already had this key present, nothing is updated, and
|
||||||
|
/// an error containing the occupied entry and the value is returned.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Basic usage:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// #![feature(map_try_insert)]
|
||||||
|
///
|
||||||
|
/// use std::collections::HashMap;
|
||||||
|
///
|
||||||
|
/// let mut map = HashMap::new();
|
||||||
|
/// assert_eq!(map.try_insert(37, "a").unwrap(), &"a");
|
||||||
|
///
|
||||||
|
/// let err = map.try_insert(37, "b").unwrap_err();
|
||||||
|
/// assert_eq!(err.entry.key(), &37);
|
||||||
|
/// assert_eq!(err.entry.get(), &"a");
|
||||||
|
/// assert_eq!(err.value, "b");
|
||||||
|
/// ```
|
||||||
|
#[unstable(feature = "map_try_insert", issue = "82766")]
|
||||||
|
pub fn try_insert(&mut self, key: K, value: V) -> Result<&mut V, OccupiedError<'_, K, V>> {
|
||||||
|
match self.entry(key) {
|
||||||
|
Occupied(entry) => Err(OccupiedError { entry, value }),
|
||||||
|
Vacant(entry) => Ok(entry.insert(value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Removes a key from the map, returning the value at the key if the key
|
/// Removes a key from the map, returning the value at the key if the key
|
||||||
/// was previously in the map.
|
/// was previously in the map.
|
||||||
///
|
///
|
||||||
|
@ -1851,6 +1884,41 @@ impl<K: Debug, V> Debug for VacantEntry<'_, K, V> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The error returned by [`try_insert`](HashMap::try_insert) when the key already exists.
|
||||||
|
///
|
||||||
|
/// Contains the occupied entry, and the value that was not inserted.
|
||||||
|
#[unstable(feature = "map_try_insert", issue = "82766")]
|
||||||
|
pub struct OccupiedError<'a, K: 'a, V: 'a> {
|
||||||
|
/// The entry in the map that was already occupied.
|
||||||
|
pub entry: OccupiedEntry<'a, K, V>,
|
||||||
|
/// The value which was not inserted, because the entry was already occupied.
|
||||||
|
pub value: V,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unstable(feature = "map_try_insert", issue = "82766")]
|
||||||
|
impl<K: Debug, V: Debug> Debug for OccupiedError<'_, K, V> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("OccupiedError")
|
||||||
|
.field("key", self.entry.key())
|
||||||
|
.field("old_value", self.entry.get())
|
||||||
|
.field("new_value", &self.value)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unstable(feature = "map_try_insert", issue = "82766")]
|
||||||
|
impl<'a, K: Debug, V: Debug> fmt::Display for OccupiedError<'a, K, V> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"failed to insert {:?}, key {:?} already exists with value {:?}",
|
||||||
|
self.value,
|
||||||
|
self.entry.key(),
|
||||||
|
self.entry.get(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
impl<'a, K, V, S> IntoIterator for &'a HashMap<K, V, S> {
|
impl<'a, K, V, S> IntoIterator for &'a HashMap<K, V, S> {
|
||||||
type Item = (&'a K, &'a V);
|
type Item = (&'a K, &'a V);
|
||||||
|
|
|
@ -470,6 +470,24 @@ impl Error for char::DecodeUtf16Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[unstable(feature = "map_try_insert", issue = "82766")]
|
||||||
|
impl<'a, K: Debug + Ord, V: Debug> Error
|
||||||
|
for crate::collections::btree_map::OccupiedError<'a, K, V>
|
||||||
|
{
|
||||||
|
#[allow(deprecated)]
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"key already exists"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unstable(feature = "map_try_insert", issue = "82766")]
|
||||||
|
impl<'a, K: Debug, V: Debug> Error for crate::collections::hash_map::OccupiedError<'a, K, V> {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"key already exists"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[stable(feature = "box_error", since = "1.8.0")]
|
#[stable(feature = "box_error", since = "1.8.0")]
|
||||||
impl<T: Error> Error for Box<T> {
|
impl<T: Error> Error for Box<T> {
|
||||||
#[allow(deprecated, deprecated_in_future)]
|
#[allow(deprecated, deprecated_in_future)]
|
||||||
|
|
|
@ -281,6 +281,7 @@
|
||||||
#![feature(linkage)]
|
#![feature(linkage)]
|
||||||
#![feature(llvm_asm)]
|
#![feature(llvm_asm)]
|
||||||
#![feature(log_syntax)]
|
#![feature(log_syntax)]
|
||||||
|
#![feature(map_try_insert)]
|
||||||
#![feature(maybe_uninit_extra)]
|
#![feature(maybe_uninit_extra)]
|
||||||
#![feature(maybe_uninit_ref)]
|
#![feature(maybe_uninit_ref)]
|
||||||
#![feature(maybe_uninit_slice)]
|
#![feature(maybe_uninit_slice)]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue