Skip to content

Commit 14d53af

Browse files
author
Cole Miller
committed
Add get_each_mut methods on RawTable and HashMap
These methods enable looking up mutable references to several entries in a table or map at once. They make use of the min_const_generics feature, which is available without a feature gate on recent nightly — but not yet stable — rustc. Hence everything added here is behind `#[cfg(feature = "nightly")]`.
1 parent b5b5be8 commit 14d53af

File tree

3 files changed

+253
-1
lines changed

3 files changed

+253
-1
lines changed

src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,20 @@ pub enum TryReserveError {
126126
},
127127
}
128128

129+
/// The error type for [`RawTable::get_each_mut`](crate::raw::RawTable::get_each_mut),
130+
/// [`HashMap::get_each_mut`], and [`HashMap::get_each_key_value_mut`].
131+
#[cfg(feature = "nightly")]
132+
#[derive(Clone, PartialEq, Eq, Debug)]
133+
pub enum UnavailableMutError {
134+
/// The requested entry is not present in the table.
135+
Absent,
136+
/// The requested entry is present, but a mutable reference to it was already created and
137+
/// returned from this call to `get_each_mut` or `get_each_key_value_mut`.
138+
///
139+
/// Includes the index of the existing mutable reference in the returned array.
140+
Duplicate(usize),
141+
}
142+
129143
/// Wrapper around `Bump` which allows it to be used as an allocator for
130144
/// `HashMap`, `HashSet` and `RawTable`.
131145
///

src/map.rs

Lines changed: 165 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1+
#[cfg(feature = "nightly")]
2+
use crate::raw::array_assume_init_shim;
13
use crate::raw::{Allocator, Bucket, Global, RawDrain, RawIntoIter, RawIter, RawTable};
24
use crate::TryReserveError;
5+
#[cfg(feature = "nightly")]
6+
use crate::UnavailableMutError;
37
use core::borrow::Borrow;
48
use core::fmt::{self, Debug};
59
use core::hash::{BuildHasher, Hash};
610
use core::iter::{FromIterator, FusedIterator};
711
use core::marker::PhantomData;
812
use core::mem;
13+
#[cfg(feature = "nightly")]
14+
use core::mem::MaybeUninit;
915
use core::ops::Index;
1016

1117
/// Default hasher for `HashMap`.
@@ -1113,6 +1119,135 @@ where
11131119
self.table.get_mut(hash, equivalent_key(k))
11141120
}
11151121

1122+
/// Attempts to get mutable references to `N` values in the map at once.
1123+
///
1124+
/// Returns an array of length `N` with the results of each query. For soundness,
1125+
/// at most one mutable reference will be returned to any value. An
1126+
/// `Err(UnavailableMutError::Duplicate(i))` in the returned array indicates that a suitable
1127+
/// key-value pair exists, but a mutable reference to the value already occurs at index `i` in
1128+
/// the returned array.
1129+
///
1130+
/// This method is available only if the `nightly` feature is enabled.
1131+
///
1132+
/// ```
1133+
/// use hashbrown::{HashMap, UnavailableMutError};
1134+
///
1135+
/// let mut libraries = HashMap::new();
1136+
/// libraries.insert("Bodleian Library".to_string(), 1602);
1137+
/// libraries.insert("Athenæum".to_string(), 1807);
1138+
/// libraries.insert("Herzogin-Anna-Amalia-Bibliothek".to_string(), 1691);
1139+
/// libraries.insert("Library of Congress".to_string(), 1800);
1140+
///
1141+
/// let got = libraries.get_each_mut([
1142+
/// "Athenæum",
1143+
/// "New York Public Library",
1144+
/// "Athenæum",
1145+
/// "Library of Congress",
1146+
/// ]);
1147+
/// assert_eq!(
1148+
/// got,
1149+
/// [
1150+
/// Ok(&mut 1807),
1151+
/// Err(UnavailableMutError::Absent),
1152+
/// Err(UnavailableMutError::Duplicate(0)),
1153+
/// Ok(&mut 1800),
1154+
/// ]
1155+
/// );
1156+
/// ```
1157+
#[cfg(feature = "nightly")]
1158+
pub fn get_each_mut<Q: ?Sized, const N: usize>(
1159+
&mut self,
1160+
ks: [&Q; N],
1161+
) -> [Result<&'_ mut V, UnavailableMutError>; N]
1162+
where
1163+
K: Borrow<Q>,
1164+
Q: Hash + Eq,
1165+
{
1166+
let mut pairs = self.get_each_inner_mut(ks);
1167+
let mut out: [MaybeUninit<Result<&'_ mut V, UnavailableMutError>>; N] =
1168+
unsafe { MaybeUninit::uninit().assume_init() };
1169+
for i in 0..N {
1170+
out[i] = MaybeUninit::new(
1171+
mem::replace(&mut pairs[i], Err(UnavailableMutError::Absent)).map(|(_, v)| v),
1172+
);
1173+
}
1174+
unsafe { array_assume_init_shim(out) }
1175+
}
1176+
1177+
/// Attempts to get mutable references to `N` values in the map at once, with immutable
1178+
/// references to the corresponding keys.
1179+
///
1180+
/// Returns an array of length `N` with the results of each query. For soundness,
1181+
/// at most one mutable reference will be returned to any value. An
1182+
/// `Err(UnavailableMutError::Duplicate(i))` in the returned array indicates that a suitable
1183+
/// key-value pair exists, but a mutable reference to the value already occurs at index `i` in
1184+
/// the returned array.
1185+
///
1186+
/// This method is available only if the `nightly` feature is enabled.
1187+
///
1188+
/// ```
1189+
/// use hashbrown::{HashMap, UnavailableMutError};
1190+
///
1191+
/// let mut libraries = HashMap::new();
1192+
/// libraries.insert("Bodleian Library".to_string(), 1602);
1193+
/// libraries.insert("Athenæum".to_string(), 1807);
1194+
/// libraries.insert("Herzogin-Anna-Amalia-Bibliothek".to_string(), 1691);
1195+
/// libraries.insert("Library of Congress".to_string(), 1800);
1196+
///
1197+
/// let got = libraries.get_each_key_value_mut([
1198+
/// "Bodleian Library",
1199+
/// "Herzogin-Anna-Amalia-Bibliothek",
1200+
/// "Herzogin-Anna-Amalia-Bibliothek",
1201+
/// "Gewandhaus",
1202+
/// ]);
1203+
/// assert_eq!(
1204+
/// got,
1205+
/// [
1206+
/// Ok((&"Bodleian Library".to_string(), &mut 1602)),
1207+
/// Ok((&"Herzogin-Anna-Amalia-Bibliothek".to_string(), &mut 1691)),
1208+
/// Err(UnavailableMutError::Duplicate(1)),
1209+
/// Err(UnavailableMutError::Absent),
1210+
/// ]
1211+
/// );
1212+
/// ```
1213+
#[cfg(feature = "nightly")]
1214+
pub fn get_each_key_value_mut<Q: ?Sized, const N: usize>(
1215+
&mut self,
1216+
ks: [&Q; N],
1217+
) -> [Result<(&'_ K, &'_ mut V), UnavailableMutError>; N]
1218+
where
1219+
K: Borrow<Q>,
1220+
Q: Hash + Eq,
1221+
{
1222+
let mut pairs = self.get_each_inner_mut(ks);
1223+
let mut out: [MaybeUninit<Result<(&'_ K, &'_ mut V), UnavailableMutError>>; N] =
1224+
unsafe { MaybeUninit::uninit().assume_init() };
1225+
for i in 0..N {
1226+
out[i] = MaybeUninit::new(
1227+
mem::replace(&mut pairs[i], Err(UnavailableMutError::Absent))
1228+
.map(|(k, v)| (&*k, v)),
1229+
);
1230+
}
1231+
unsafe { array_assume_init_shim(out) }
1232+
}
1233+
1234+
#[cfg(feature = "nightly")]
1235+
fn get_each_inner_mut<Q: ?Sized, const N: usize>(
1236+
&mut self,
1237+
ks: [&Q; N],
1238+
) -> [Result<&'_ mut (K, V), UnavailableMutError>; N]
1239+
where
1240+
K: Borrow<Q>,
1241+
Q: Hash + Eq,
1242+
{
1243+
let mut hashes = [0_u64; N];
1244+
for i in 0..N {
1245+
hashes[i] = make_hash::<K, Q, S>(&self.hash_builder, ks[i]);
1246+
}
1247+
self.table
1248+
.get_each_mut(hashes, |i, (k, _)| ks[i].eq(k.borrow()))
1249+
}
1250+
11161251
/// Inserts a key-value pair into the map.
11171252
///
11181253
/// If the map did not have this key present, [`None`] is returned.
@@ -3315,6 +3450,7 @@ mod test_map {
33153450
use super::{HashMap, RawEntryMut};
33163451
use crate::TryReserveError::*;
33173452
use rand::{rngs::SmallRng, Rng, SeedableRng};
3453+
use std::borrow::ToOwned;
33183454
use std::cell::RefCell;
33193455
use std::usize;
33203456
use std::vec::Vec;
@@ -4682,7 +4818,6 @@ mod test_map {
46824818
#[test]
46834819
fn test_const_with_hasher() {
46844820
use core::hash::BuildHasher;
4685-
use std::borrow::ToOwned;
46864821
use std::collections::hash_map::DefaultHasher;
46874822

46884823
#[derive(Clone)]
@@ -4702,4 +4837,33 @@ mod test_map {
47024837
map.insert(17, "seventeen".to_owned());
47034838
assert_eq!("seventeen", map[&17]);
47044839
}
4840+
4841+
#[test]
4842+
#[cfg(feature = "nightly")]
4843+
fn test_get_each_mut() {
4844+
use crate::UnavailableMutError::*;
4845+
4846+
let mut map = HashMap::new();
4847+
map.insert("foo".to_owned(), 0);
4848+
map.insert("bar".to_owned(), 10);
4849+
map.insert("baz".to_owned(), 20);
4850+
map.insert("qux".to_owned(), 30);
4851+
4852+
let xs = map.get_each_mut(["foo", "dud", "foo", "qux"]);
4853+
assert_eq!(
4854+
xs,
4855+
[Ok(&mut 0), Err(Absent), Err(Duplicate(0)), Ok(&mut 30)]
4856+
);
4857+
4858+
let ys = map.get_each_key_value_mut(["bar", "baz", "baz", "dip"]);
4859+
assert_eq!(
4860+
ys,
4861+
[
4862+
Ok((&"bar".to_owned(), &mut 10)),
4863+
Ok((&"baz".to_owned(), &mut 20)),
4864+
Err(Duplicate(1)),
4865+
Err(Absent),
4866+
]
4867+
);
4868+
}
47054869
}

src/raw/mod.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
use crate::alloc::alloc::{handle_alloc_error, Layout};
22
use crate::scopeguard::guard;
33
use crate::TryReserveError;
4+
#[cfg(feature = "nightly")]
5+
use crate::UnavailableMutError;
46
use core::hint;
57
use core::iter::FusedIterator;
68
use core::marker::PhantomData;
79
use core::mem;
810
use core::mem::ManuallyDrop;
11+
#[cfg(feature = "nightly")]
12+
use core::mem::MaybeUninit;
913
use core::ptr::NonNull;
1014

1115
cfg_if! {
@@ -388,6 +392,20 @@ struct RawTableInner<A> {
388392
alloc: A,
389393
}
390394

395+
/// We need this workaround because `transmute` can't be used on arrays of generic length.
396+
///
397+
/// Nightly has a `MaybeUninit::array_assume_init` function, but it's behind a feature gate (see
398+
/// rust-lang/rust#80908), so for now we just open-code it here.
399+
///
400+
/// TODO replace this with `MaybeUninit::array_assume_init` if/once that gets stabilized
401+
#[cfg(feature = "nightly")]
402+
pub(crate) unsafe fn array_assume_init_shim<T, const N: usize>(
403+
array: [MaybeUninit<T>; N],
404+
) -> [T; N] {
405+
core::intrinsics::assert_inhabited::<[T; N]>();
406+
(&array as *const _ as *const [T; N]).read()
407+
}
408+
391409
impl<T> RawTable<T, Global> {
392410
/// Creates a new empty hash table without allocating any memory.
393411
///
@@ -944,6 +962,62 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
944962
}
945963
}
946964

965+
/// Attempts to get mutable references to `N` entries in the table at once.
966+
///
967+
/// Returns an array of length `N` with the results of each query. For soundness,
968+
/// at most one mutable reference will be returned to any entry. An
969+
/// `Err(UnavailableMutError::Duplicate(i))` in the returned array indicates that a suitable
970+
/// entry exists, but a mutable reference to it already occurs at index `i` in the returned
971+
/// array.
972+
///
973+
/// The `eq` argument should be a closure such that `eq(i, k)` returns true if `k` is equal to
974+
/// the `i`th key to be looked up.
975+
///
976+
/// This method is available only if the `nightly` feature is enabled.
977+
#[cfg(feature = "nightly")]
978+
pub fn get_each_mut<const N: usize>(
979+
&mut self,
980+
hashes: [u64; N],
981+
mut eq: impl FnMut(usize, &T) -> bool,
982+
) -> [Result<&'_ mut T, UnavailableMutError>; N] {
983+
// Collect the requested buckets.
984+
let mut buckets: [MaybeUninit<Option<Bucket<T>>>; N] =
985+
unsafe { MaybeUninit::uninit().assume_init() };
986+
for i in 0..N {
987+
buckets[i] = MaybeUninit::new(self.find(hashes[i], |k| eq(i, k)));
988+
}
989+
let buckets: [Option<Bucket<T>>; N] = unsafe { array_assume_init_shim(buckets) };
990+
991+
// Walk through the buckets, checking for duplicates and building up the output array.
992+
let mut out: [MaybeUninit<Result<&'_ mut T, UnavailableMutError>>; N] =
993+
unsafe { MaybeUninit::uninit().assume_init() };
994+
for i in 0..N {
995+
out[i] = MaybeUninit::new(
996+
#[allow(clippy::never_loop)]
997+
'outer: loop {
998+
for j in 0..i {
999+
match (&buckets[j], &buckets[i]) {
1000+
// These two buckets are the same, and we can't safely return a second
1001+
// mutable reference to the same entry.
1002+
(Some(prev), Some(cur)) if unsafe { prev.as_ptr() == cur.as_ptr() } => {
1003+
break 'outer Err(UnavailableMutError::Duplicate(j));
1004+
}
1005+
_ => {}
1006+
}
1007+
}
1008+
// This bucket is distinct from all previous buckets (or it doesn't exist), so
1009+
// we're clear to return the result of the lookup.
1010+
break match &buckets[i] {
1011+
None => Err(UnavailableMutError::Absent),
1012+
Some(bkt) => unsafe { Ok(bkt.as_mut()) },
1013+
};
1014+
},
1015+
)
1016+
}
1017+
1018+
unsafe { array_assume_init_shim(out) }
1019+
}
1020+
9471021
/// Returns the number of elements the map can hold without reallocating.
9481022
///
9491023
/// This number is a lower bound; the table might be able to hold

0 commit comments

Comments
 (0)