diff --git a/src/libstd/collections/hash/map.rs b/src/libstd/collections/hash/map.rs index 820b47af7154..91626b8d16b6 100644 --- a/src/libstd/collections/hash/map.rs +++ b/src/libstd/collections/hash/map.rs @@ -9,7 +9,6 @@ // except according to those terms. use self::Entry::*; -use self::SearchResult::*; use self::VacantEntryState::*; use borrow::Borrow; @@ -26,7 +25,6 @@ use super::table::{ Bucket, EmptyBucket, FullBucket, - FullBucketImm, FullBucketMut, RawTable, SafeHash @@ -342,10 +340,11 @@ pub struct HashMap { } /// Search for a pre-hashed key. +#[inline] fn search_hashed(table: M, hash: SafeHash, mut is_match: F) - -> SearchResult where + -> InternalEntry where M: Deref>, F: FnMut(&K) -> bool, { @@ -353,37 +352,50 @@ fn search_hashed(table: M, // undefined behavior when Bucket::new gets the raw bucket in this // case, immediately return the appropriate search result. if table.capacity() == 0 { - return TableRef(table); + return InternalEntry::TableIsEmpty; } - let size = table.size(); + let size = table.size() as isize; let mut probe = Bucket::new(table, hash); - let ib = probe.index(); + let ib = probe.index() as isize; - while probe.index() != ib + size { + loop { let full = match probe.peek() { - Empty(b) => return TableRef(b.into_table()), // hit an empty bucket - Full(b) => b + Empty(bucket) => { + // Found a hole! + return InternalEntry::Vacant { + hash: hash, + elem: NoElem(bucket), + }; + } + Full(bucket) => bucket }; - if full.distance() + ib < full.index() { + let robin_ib = full.index() as isize - full.displacement() as isize; + + if ib < robin_ib { + // Found a luckier bucket than me. // We can finish the search early if we hit any bucket // with a lower distance to initial bucket than we've probed. - return TableRef(full.into_table()); + return InternalEntry::Vacant { + hash: hash, + elem: NeqElem(full, robin_ib as usize), + }; } // If the hash doesn't match, it can't be this one.. if hash == full.hash() { // If the key doesn't match, it can't be this one.. if is_match(full.read().0) { - return FoundExisting(full); + return InternalEntry::Occupied { + elem: full + }; } } probe = full.next(); + assert!(probe.index() as isize != ib + size + 1); } - - TableRef(probe.into_table()) } fn pop_internal(starting_bucket: FullBucketMut) -> (K, V) { @@ -462,25 +474,6 @@ fn robin_hood<'a, K: 'a, V: 'a>(mut bucket: FullBucketMut<'a, K, V>, } } -/// A result that works like Option> but preserves -/// the reference that grants us access to the table in any case. -enum SearchResult { - // This is an entry that holds the given key: - FoundExisting(FullBucket), - - // There was no such entry. The reference is given back: - TableRef(M) -} - -impl SearchResult { - fn into_option(self) -> Option> { - match self { - FoundExisting(bucket) => Some(bucket), - TableRef(_) => None - } - } -} - impl HashMap where K: Eq + Hash, S: BuildHasher { @@ -491,20 +484,20 @@ impl HashMap /// Search for a key, yielding the index if it's found in the hashtable. /// If you already have the hash for the key lying around, use /// search_hashed. - fn search<'a, Q: ?Sized>(&'a self, q: &Q) -> Option> + #[inline] + fn search<'a, Q: ?Sized>(&'a self, q: &Q) -> InternalEntry> where K: Borrow, Q: Eq + Hash { let hash = self.make_hash(q); search_hashed(&self.table, hash, |k| q.eq(k.borrow())) - .into_option() } - fn search_mut<'a, Q: ?Sized>(&'a mut self, q: &Q) -> Option> + #[inline] + fn search_mut<'a, Q: ?Sized>(&'a mut self, q: &Q) -> InternalEntry> where K: Borrow, Q: Eq + Hash { let hash = self.make_hash(q); search_hashed(&mut self.table, hash, |k| q.eq(k.borrow())) - .into_option() } // The caller should ensure that invariants by Robin Hood Hashing hold. @@ -824,53 +817,19 @@ impl HashMap /// /// If the key already exists, the hashtable will be returned untouched /// and a reference to the existing element will be returned. - fn insert_hashed_nocheck(&mut self, hash: SafeHash, k: K, v: V) -> &mut V { - self.insert_or_replace_with(hash, k, v, |_, _, _, _| ()) - } - - fn insert_or_replace_with<'a, F>(&'a mut self, - hash: SafeHash, - k: K, - v: V, - mut found_existing: F) - -> &'a mut V where - F: FnMut(&mut K, &mut V, K, V), - { - // Worst case, we'll find one empty bucket among `size + 1` buckets. - let size = self.table.size(); - let mut probe = Bucket::new(&mut self.table, hash); - let ib = probe.index(); - - loop { - let mut bucket = match probe.peek() { - Empty(bucket) => { - // Found a hole! - return bucket.put(hash, k, v).into_mut_refs().1; - } - Full(bucket) => bucket - }; - - // hash matches? - if bucket.hash() == hash { - // key matches? - if k == *bucket.read_mut().0 { - let (bucket_k, bucket_v) = bucket.into_mut_refs(); - debug_assert!(k == *bucket_k); - // Key already exists. Get its reference. - found_existing(bucket_k, bucket_v, k, v); - return bucket_v; - } + fn insert_hashed_nocheck(&mut self, hash: SafeHash, k: K, v: V) -> Option { + let entry = search_hashed(&mut self.table, hash, |key| *key == k).into_entry(k); + match entry { + Some(Occupied(mut elem)) => { + Some(elem.insert(v)) } - - let robin_ib = bucket.index() as isize - bucket.distance() as isize; - - if (ib as isize) < robin_ib { - // Found a luckier bucket than me. Better steal his spot. - return robin_hood(bucket, robin_ib as usize, hash, k, v); + Some(Vacant(elem)) => { + elem.insert(v); + None + } + None => { + unreachable!() } - - probe = bucket.next(); - assert!(probe.index() != ib + size + 1); } } @@ -997,9 +956,7 @@ impl HashMap pub fn entry(&mut self, key: K) -> Entry { // Gotta resize now. self.reserve(1); - - let hash = self.make_hash(&key); - search_entry_hashed(&mut self.table, hash, key) + self.search_mut(&key).into_entry(key).expect("unreachable") } /// Returns the number of elements in the map. @@ -1102,7 +1059,7 @@ impl HashMap pub fn get(&self, k: &Q) -> Option<&V> where K: Borrow, Q: Hash + Eq { - self.search(k).map(|bucket| bucket.into_refs().1) + self.search(k).into_occupied_bucket().map(|bucket| bucket.into_refs().1) } /// Returns true if the map contains a value for the specified key. @@ -1125,7 +1082,7 @@ impl HashMap pub fn contains_key(&self, k: &Q) -> bool where K: Borrow, Q: Hash + Eq { - self.search(k).is_some() + self.search(k).into_occupied_bucket().is_some() } /// Returns a mutable reference to the value corresponding to the key. @@ -1150,7 +1107,7 @@ impl HashMap pub fn get_mut(&mut self, k: &Q) -> Option<&mut V> where K: Borrow, Q: Hash + Eq { - self.search_mut(k).map(|bucket| bucket.into_mut_refs().1) + self.search_mut(k).into_occupied_bucket().map(|bucket| bucket.into_mut_refs().1) } /// Inserts a key-value pair into the map. @@ -1181,12 +1138,7 @@ impl HashMap pub fn insert(&mut self, k: K, v: V) -> Option { let hash = self.make_hash(&k); self.reserve(1); - - let mut retval = None; - self.insert_or_replace_with(hash, k, v, |_, val_ref, _, val| { - retval = Some(replace(val_ref, val)); - }); - retval + self.insert_hashed_nocheck(hash, k, v) } /// Removes a key from the map, returning the value at the key if the key @@ -1214,54 +1166,7 @@ impl HashMap return None } - self.search_mut(k).map(|bucket| pop_internal(bucket).1) - } -} - -fn search_entry_hashed<'a, K: Eq, V>(table: &'a mut RawTable, hash: SafeHash, k: K) - -> Entry<'a, K, V> -{ - // Worst case, we'll find one empty bucket among `size + 1` buckets. - let size = table.size(); - let mut probe = Bucket::new(table, hash); - let ib = probe.index(); - - loop { - let bucket = match probe.peek() { - Empty(bucket) => { - // Found a hole! - return Vacant(VacantEntry { - hash: hash, - key: k, - elem: NoElem(bucket), - }); - }, - Full(bucket) => bucket - }; - - // hash matches? - if bucket.hash() == hash { - // key matches? - if k == *bucket.read().0 { - return Occupied(OccupiedEntry{ - elem: bucket, - }); - } - } - - let robin_ib = bucket.index() as isize - bucket.distance() as isize; - - if (ib as isize) < robin_ib { - // Found a luckier bucket than me. Better steal his spot. - return Vacant(VacantEntry { - hash: hash, - key: k, - elem: NeqElem(bucket, robin_ib as usize), - }); - } - - probe = bucket.next(); - assert!(probe.index() != ib + size + 1); + self.search_mut(k).into_occupied_bucket().map(|bucket| pop_internal(bucket).1) } } @@ -1382,18 +1287,46 @@ pub struct Drain<'a, K: 'a, V: 'a> { inner: iter::Map, fn((SafeHash, K, V)) -> (K, V)> } -/// A view into a single occupied location in a HashMap. -#[stable(feature = "rust1", since = "1.0.0")] -pub struct OccupiedEntry<'a, K: 'a, V: 'a> { - elem: FullBucket>, +enum InternalEntry { + Occupied { + elem: FullBucket, + }, + Vacant { + hash: SafeHash, + elem: VacantEntryState, + }, + TableIsEmpty, } -/// A view into a single empty location in a HashMap. -#[stable(feature = "rust1", since = "1.0.0")] -pub struct VacantEntry<'a, K: 'a, V: 'a> { - hash: SafeHash, - key: K, - elem: VacantEntryState>, +impl InternalEntry { + #[inline] + fn into_occupied_bucket(self) -> Option> { + match self { + InternalEntry::Occupied { elem } => Some(elem), + _ => None, + } + } +} + +impl<'a, K, V> InternalEntry> { + #[inline] + fn into_entry(self, key: K) -> Option> { + match self { + InternalEntry::Occupied { elem } => { + Some(Occupied(OccupiedEntry { + elem: elem + })) + } + InternalEntry::Vacant { hash, elem } => { + Some(Vacant(VacantEntry { + hash: hash, + key: key, + elem: elem, + })) + } + InternalEntry::TableIsEmpty => None + } + } } /// A view into a single location in a map, which may be vacant or occupied. @@ -1412,6 +1345,20 @@ pub enum Entry<'a, K: 'a, V: 'a> { ), } +/// A view into a single occupied location in a HashMap. +#[stable(feature = "rust1", since = "1.0.0")] +pub struct OccupiedEntry<'a, K: 'a, V: 'a> { + elem: FullBucket>, +} + +/// A view into a single empty location in a HashMap. +#[stable(feature = "rust1", since = "1.0.0")] +pub struct VacantEntry<'a, K: 'a, V: 'a> { + hash: SafeHash, + key: K, + elem: VacantEntryState>, +} + /// Possible states of a VacantEntry. enum VacantEntryState { /// The index is occupied, but the key to insert has precedence, @@ -1703,7 +1650,7 @@ impl super::Recover for HashMap type Key = K; fn get(&self, key: &Q) -> Option<&K> { - self.search(key).map(|bucket| bucket.into_refs().0) + self.search(key).into_occupied_bucket().map(|bucket| bucket.into_refs().0) } fn take(&mut self, key: &Q) -> Option { @@ -1711,18 +1658,21 @@ impl super::Recover for HashMap return None } - self.search_mut(key).map(|bucket| pop_internal(bucket).0) + self.search_mut(key).into_occupied_bucket().map(|bucket| pop_internal(bucket).0) } fn replace(&mut self, key: K) -> Option { let hash = self.make_hash(&key); self.reserve(1); - let mut retkey = None; - self.insert_or_replace_with(hash, key, (), |key_ref, _, key, _| { - retkey = Some(replace(key_ref, key)); - }); - retkey + match search_hashed(&mut self.table, hash, |k| *k == key) { + InternalEntry::Occupied { mut elem } => { + Some(mem::replace(elem.read_mut().0, key)) + } + _ => { + None + } + } } } diff --git a/src/libstd/collections/hash/table.rs b/src/libstd/collections/hash/table.rs index 3a7ff0c1e220..1a29c2c159a8 100644 --- a/src/libstd/collections/hash/table.rs +++ b/src/libstd/collections/hash/table.rs @@ -180,6 +180,8 @@ impl RawBucket { } } +// Uncomment dead code when it's needed. + // Buckets hold references to the table. impl FullBucket { /// Borrow a reference to the table. @@ -201,17 +203,17 @@ impl EmptyBucket { pub fn table(&self) -> &M { &self.table } - /// Move out the reference to the table. - pub fn into_table(self) -> M { - self.table - } + // /// Move out the reference to the table. + // pub fn into_table(self) -> M { + // self.table + // } } impl Bucket { - /// Move out the reference to the table. - pub fn into_table(self) -> M { - self.table - } + // /// Move out the reference to the table. + // pub fn into_table(self) -> M { + // self.table + // } /// Get the raw index. pub fn index(&self) -> usize { self.idx