Skip to content

Commit

Permalink
Prepare hashbrown for inclusion in the standard library
Browse files Browse the repository at this point in the history
  • Loading branch information
Amanieu committed Mar 26, 2019
1 parent edf79d0 commit 332db4d
Show file tree
Hide file tree
Showing 8 changed files with 797 additions and 174 deletions.
9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ categories = ["data-structures", "no-std"]
exclude = [".travis.yml", "bors.toml"]

[dependencies]
byteorder = { version = "1.0", default-features = false }
scopeguard = { version = "0.3", default-features = false }

# For external trait impls
rayon = { version = "1.0", optional = true }
serde = { version = "1.0.25", default-features = false, optional = true }

# When built as part of libstd
core = { version = "1.0.0", optional = true, package = "rustc-std-workspace-core" }
compiler_builtins = { version = "0.1.2", optional = true }
alloc = { version = "1.0.0", optional = true, package = "rustc-std-workspace-alloc" }

[dev-dependencies]
lazy_static = "~1.2"
rand = "0.5.1"
Expand All @@ -28,3 +30,4 @@ serde_test = "1.0"
[features]
default = []
nightly = []
rustc-dep-of-std = ["nightly", "core", "compiler_builtins", "alloc"]
24 changes: 13 additions & 11 deletions src/fx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ use core::hash::{BuildHasherDefault, Hasher};
use core::mem::size_of;
use core::ops::BitXor;

use byteorder::{ByteOrder, NativeEndian};

/// Type alias for a `HashBuilder` using the `fx` hash algorithm.
pub type FxHashBuilder = BuildHasherDefault<FxHasher>;

Expand Down Expand Up @@ -47,25 +45,29 @@ impl FxHasher {
impl Hasher for FxHasher {
#[inline]
fn write(&mut self, mut bytes: &[u8]) {
#[cfg(target_pointer_width = "32")]
#[allow(clippy::cast_possible_truncation)]
let read_usize = |bytes| NativeEndian::read_u32(bytes) as usize;
#[cfg(target_pointer_width = "64")]
#[allow(clippy::cast_possible_truncation)]
let read_usize = |bytes| NativeEndian::read_u64(bytes) as usize;
macro_rules! read_bytes {
($ty:ty, $src:expr) => {{
assert!(size_of::<$ty>() <= $src.len());
let mut data: $ty = 0;
unsafe {
$src.as_ptr().copy_to_nonoverlapping(&mut data as *mut $ty as *mut u8, size_of::<$ty>());
}
data
}};
}

let mut hash = Self { hash: self.hash };
assert!(size_of::<usize>() <= 8);
while bytes.len() >= size_of::<usize>() {
hash.add_to_hash(read_usize(bytes));
hash.add_to_hash(read_bytes!(usize, bytes) as usize);
bytes = &bytes[size_of::<usize>()..];
}
if (size_of::<usize>() > 4) && (bytes.len() >= 4) {
hash.add_to_hash(NativeEndian::read_u32(bytes) as usize);
hash.add_to_hash(read_bytes!(u32, bytes) as usize);
bytes = &bytes[4..];
}
if (size_of::<usize>() > 2) && bytes.len() >= 2 {
hash.add_to_hash(NativeEndian::read_u16(bytes) as usize);
hash.add_to_hash(read_bytes!(u16, bytes) as usize);
bytes = &bytes[2..];
}
if (size_of::<usize>() > 1) && !bytes.is_empty() {
Expand Down
10 changes: 6 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,16 @@
#![allow(clippy::module_name_repetitions)]

#[cfg(test)]
#[macro_use]
#[allow(unused_imports)]
#[cfg_attr(feature = "rayon", macro_use)]
extern crate std;
#[cfg(test)]
extern crate rand;

#[cfg(feature = "nightly")]
#[cfg_attr(test, macro_use)]
extern crate alloc;
extern crate byteorder;
#[cfg(feature = "rayon")]
extern crate rayon;
extern crate scopeguard;
#[cfg(feature = "serde")]
extern crate serde;
#[cfg(not(feature = "nightly"))]
Expand All @@ -53,11 +50,16 @@ mod fx;
mod map;
mod raw;
mod set;
#[cfg(feature = "rustc-dep-of-std")]
mod rustc_entry;

pub mod hash_map {
//! A hash map implemented with quadratic probing and SIMD lookup.
pub use map::*;

#[cfg(feature = "rustc-dep-of-std")]
pub use rustc_entry::*;

#[cfg(feature = "rayon")]
/// [rayon]-based parallel iterator types for hash maps.
/// You will rarely need to interact with it directly unless you have need
Expand Down
43 changes: 14 additions & 29 deletions src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ pub use fx::FxHashBuilder as DefaultHashBuilder;
/// }
///
/// impl Viking {
/// /// Create a new Viking.
/// /// Creates a new Viking.
/// fn new(name: &str, country: &str) -> Viking {
/// Viking { name: name.to_string(), country: country.to_string() }
/// }
Expand Down Expand Up @@ -186,12 +186,12 @@ pub use fx::FxHashBuilder as DefaultHashBuilder;

#[derive(Clone)]
pub struct HashMap<K, V, S = DefaultHashBuilder> {
hash_builder: S,
pub(crate) hash_builder: S,
pub(crate) table: RawTable<(K, V)>,
}

#[inline]
fn make_hash<K: Hash + ?Sized>(hash_builder: &impl BuildHasher, val: &K) -> u64 {
pub(crate) fn make_hash<K: Hash + ?Sized>(hash_builder: &impl BuildHasher, val: &K) -> u64 {
let mut state = hash_builder.build_hasher();
val.hash(&mut state);
state.finish()
Expand Down Expand Up @@ -232,8 +232,6 @@ impl<K, V> HashMap<K, V, DefaultHashBuilder> {
}

impl<K, V, S> HashMap<K, V, S>
where
S: BuildHasher,
{
/// Creates an empty `HashMap` which will use the given hash builder to hash
/// keys.
Expand Down Expand Up @@ -486,7 +484,7 @@ where
self.table.len()
}

/// Returns true if the map contains no elements.
/// Returns `true` if the map contains no elements.
///
/// # Examples
///
Expand Down Expand Up @@ -757,7 +755,7 @@ where
})
}

/// Returns true if the map contains a value for the specified key.
/// Returns `true` if the map contains a value for the specified key.
///
/// The key may be any borrowed form of the map's key type, but
/// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
Expand Down Expand Up @@ -1220,7 +1218,7 @@ impl<'a, K, V: Debug> fmt::Debug for Values<'a, K, V> {
/// [`drain`]: struct.HashMap.html#method.drain
/// [`HashMap`]: struct.HashMap.html
pub struct Drain<'a, K: 'a, V: 'a> {
pub(super) inner: RawDrain<'a, (K, V)>,
inner: RawDrain<'a, (K, V)>,
}

impl<'a, K, V> Drain<'a, K, V> {
Expand Down Expand Up @@ -1300,9 +1298,8 @@ pub struct RawEntryBuilder<'a, K: 'a, V: 'a, S: 'a> {
impl<'a, K, V, S> RawEntryBuilderMut<'a, K, V, S>
where
S: BuildHasher,
K: Eq + Hash,
{
/// Create a `RawEntryMut` from the given key.
/// Creates a `RawEntryMut` from the given key.
#[inline]
#[allow(clippy::wrong_self_convention)]
pub fn from_key<Q: ?Sized>(self, k: &Q) -> RawEntryMut<'a, K, V, S>
Expand All @@ -1315,7 +1312,7 @@ where
self.from_key_hashed_nocheck(hasher.finish(), k)
}

/// Create a `RawEntryMut` from the given key and its hash.
/// Creates a `RawEntryMut` from the given key and its hash.
#[inline]
#[allow(clippy::wrong_self_convention)]
pub fn from_key_hashed_nocheck<Q: ?Sized>(self, hash: u64, k: &Q) -> RawEntryMut<'a, K, V, S>
Expand All @@ -1331,7 +1328,7 @@ impl<'a, K, V, S> RawEntryBuilderMut<'a, K, V, S>
where
S: BuildHasher,
{
/// Create a `RawEntryMut` from the given hash.
/// Creates a `RawEntryMut` from the given hash.
#[inline]
#[allow(clippy::wrong_self_convention)]
pub fn from_hash<F>(self, hash: u64, is_match: F) -> RawEntryMut<'a, K, V, S>
Expand Down Expand Up @@ -1706,7 +1703,7 @@ pub enum Entry<'a, K: 'a, V: 'a, S: 'a> {
Vacant(VacantEntry<'a, K, V, S>),
}

impl<'a, K: 'a + Debug + Eq + Hash, V: 'a + Debug, S: 'a> Debug for Entry<'a, K, V, S> {
impl<'a, K: 'a + Debug, V: 'a + Debug, S: 'a> Debug for Entry<'a, K, V, S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Entry::Vacant(ref v) => f.debug_tuple("Entry").field(v).finish(),
Expand Down Expand Up @@ -1759,17 +1756,13 @@ pub struct VacantEntry<'a, K: 'a, V: 'a, S: 'a> {
table: &'a mut HashMap<K, V, S>,
}

impl<'a, K: 'a + Debug + Eq + Hash, V: 'a, S> Debug for VacantEntry<'a, K, V, S> {
impl<'a, K: 'a + Debug, V: 'a, S> Debug for VacantEntry<'a, K, V, S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("VacantEntry").field(self.key()).finish()
}
}

impl<'a, K, V, S> IntoIterator for &'a HashMap<K, V, S>
where
K: Eq + Hash,
S: BuildHasher,
{
impl<'a, K, V, S> IntoIterator for &'a HashMap<K, V, S> {
type Item = (&'a K, &'a V);
type IntoIter = Iter<'a, K, V>;

Expand All @@ -1779,11 +1772,7 @@ where
}
}

impl<'a, K, V, S> IntoIterator for &'a mut HashMap<K, V, S>
where
K: Eq + Hash,
S: BuildHasher,
{
impl<'a, K, V, S> IntoIterator for &'a mut HashMap<K, V, S> {
type Item = (&'a K, &'a mut V);
type IntoIter = IterMut<'a, K, V>;

Expand All @@ -1793,11 +1782,7 @@ where
}
}

impl<K, V, S> IntoIterator for HashMap<K, V, S>
where
K: Eq + Hash,
S: BuildHasher,
{
impl<K, V, S> IntoIterator for HashMap<K, V, S> {
type Item = (K, V);
type IntoIter = IntoIter<K, V>;

Expand Down
21 changes: 17 additions & 4 deletions src/raw/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use core::mem;
use core::mem::ManuallyDrop;
use core::ops::Range;
use core::ptr::NonNull;
use scopeguard::guard;
use CollectionAllocErr;

// Branch prediction hint. This is currently only available on nightly but it
Expand Down Expand Up @@ -60,6 +59,9 @@ cfg_if! {

mod bitmask;

mod scopeguard;

use self::scopeguard::guard;
use self::bitmask::BitMask;
use self::imp::Group;

Expand Down Expand Up @@ -714,8 +716,10 @@ impl<T> RawTable<T> {
// This may panic.
let hash = hasher(item.as_ref());

// We can use a simpler version of insert() here since there are no
// DELETED entries.
// We can use a simpler version of insert() here since:
// - there are no DELETED entries.
// - we know there is enough space in the table.
// - all elements are unique.
let index = new_table.find_insert_slot(hash);
new_table.set_ctrl(index, h2(hash));
new_table.bucket(index).write(item.read());
Expand All @@ -737,7 +741,16 @@ impl<T> RawTable<T> {
#[inline]
pub fn insert(&mut self, hash: u64, value: T, hasher: impl Fn(&T) -> u64) -> Bucket<T> {
self.reserve(1, hasher);
self.insert_no_grow(hash, value)
}

/// Inserts a new element into the table, without growing the table.
///
/// There must be enough space in the table to insert the new element.
///
/// This does not check if the given element already exists in the table.
#[inline]
pub fn insert_no_grow(&mut self, hash: u64, value: T) -> Bucket<T> {
unsafe {
let index = self.find_insert_slot(hash);
let bucket = self.bucket(index);
Expand Down Expand Up @@ -941,7 +954,7 @@ impl<T> IntoIterator for RawTable<T> {
}
}

/// Iterator over a a sub-range of a table. Unlike `RawIter` this iterator does
/// Iterator over a sub-range of a table. Unlike `RawIter` this iterator does
/// not track an item count.
pub struct RawIterRange<T> {
// Using *const here for covariance
Expand Down
49 changes: 49 additions & 0 deletions src/raw/scopeguard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Extracted from the scopeguard crate
use core::ops::{Deref, DerefMut};

pub struct ScopeGuard<T, F>
where
F: FnMut(&mut T),
{
dropfn: F,
value: T,
}

#[inline]
pub fn guard<T, F>(value: T, dropfn: F) -> ScopeGuard<T, F>
where
F: FnMut(&mut T),
{
ScopeGuard { dropfn, value }
}

impl<T, F> Deref for ScopeGuard<T, F>
where
F: FnMut(&mut T),
{
type Target = T;
#[inline]
fn deref(&self) -> &T {
&self.value
}
}

impl<T, F> DerefMut for ScopeGuard<T, F>
where
F: FnMut(&mut T),
{
#[inline]
fn deref_mut(&mut self) -> &mut T {
&mut self.value
}
}

impl<T, F> Drop for ScopeGuard<T, F>
where
F: FnMut(&mut T),
{
#[inline]
fn drop(&mut self) {
(self.dropfn)(&mut self.value)
}
}
Loading

0 comments on commit 332db4d

Please sign in to comment.