Merge branch 'master' into update-deps

This commit is contained in:
Chojan Shang
2021-08-23 19:12:56 +08:00
committed by GitHub
23 changed files with 567 additions and 410 deletions
+98
View File
@@ -0,0 +1,98 @@
name: Rust
on:
push:
branches-ignore:
- trying.tmp
- staging.tmp
pull_request:
jobs:
miri:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- env:
TARGET: x86_64-unknown-linux-gnu
run: sh ci/miri.sh
rustfmt_clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- env:
TARGET: i586-unknown-linux-gnu
run: sh ci/tools.sh
test:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.channel }}
override: true
- env:
TARGET: ${{ matrix.target }}
CHANNEL: ${{ matrix.channel }}
CROSS: ${{ matrix.target != 'x86_64-unknown-linux-gnu' && '1' || '0' }}
NO_STD: ${{ matrix.target == 'thumbv6m-none-eabi' && '1' || '0' }}
run: sh ci/run.sh
strategy:
matrix:
os: [ubuntu-latest]
target: [
x86_64-unknown-linux-gnu,
i686-unknown-linux-gnu,
i586-unknown-linux-gnu,
armv7-unknown-linux-gnueabihf,
aarch64-unknown-linux-gnu,
thumbv6m-none-eabi,
x86_64-pc-windows-gnu,
]
channel: [1.49.0, nightly]
include:
- os: macos-latest
target: x86_64-apple-darwin
channel: nightly
- os: windows-latest
target: x86_64-pc-windows-msvc
channel: nightly
- os: macos-latest
target: x86_64-apple-darwin
channel: 1.49.0
- os: windows-latest
target: x86_64-pc-windows-msvc
channel: 1.49.0
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
channel: beta
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
channel: stable
# These jobs doesn't actually test anything, but they're only used to tell
# bors the build completed, as there is no practical way to detect when a
# workflow is successful listening to webhooks only.
#
# ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
end-success:
name: bors build finished
if: github.event.pusher.name == 'bors' && success()
runs-on: ubuntu-latest
needs: [miri, rustfmt_clippy, test]
steps:
- name: Mark the job as successful
run: exit 0
end-failure:
name: bors build finished
if: github.event.pusher.name == 'bors' && (failure() || cancelled())
runs-on: ubuntu-latest
needs: [miri, rustfmt_clippy, test]
steps:
- name: Mark the job as a failure
run: exit 1
-78
View File
@@ -1,78 +0,0 @@
language: rust
rust: nightly
matrix:
fast_finish: true
include:
- name: "miri"
env: TARGET=x86_64-unknown-linux-gnu
script: sh ci/miri.sh
- name: "rustfmt/clippy"
env: TARGET=i586-unknown-linux-gnu
script: sh ci/tools.sh
- name: "docs"
env: TARGET=x86_64-unknown-linux-gnu
script:
- cargo -vv doc --features nightly,serde,rayon,raw
- echo '<meta http-equiv=refresh content=0;url=hashbrown/index.html>' > target/doc/index.html
deploy:
provider: pages
skip-cleanup: true
github-token: $GITHUB_TOKEN
local-dir: target/doc
keep-history: false
on:
branch: master
# Tier 1 targets:
- name: "x86_64-unknown-linux-gnu"
env: TARGET=x86_64-unknown-linux-gnu
- name: "x86_64-unknown-linux-gnu (beta)"
rust: beta
env: TARGET=x86_64-unknown-linux-gnu
- name: "x86_64-unknown-linux-gnu (stable)"
rust: stable
env: TARGET=x86_64-unknown-linux-gnu
- name: "x86_64-unknown-linux-gnu (Rust 1.49.0)"
rust: 1.49.0
env: TARGET=x86_64-unknown-linux-gnu
- name: "i686-unknown-linux-gnu"
env: TARGET=i686-unknown-linux-gnu CROSS=1
- name: "x86_64-apple-darwin"
env: TARGET=x86_64-apple-darwin
os: osx
osx_image: xcode10
- name: "x86_64-pc-windows-msvc"
env: TARGET=x86_64-pc-windows-msvc
os: windows
- name: "x86_64-pc-windows-gnu"
env: TARGET=x86_64-pc-windows-gnu CROSS=1
# This target is not supported by cross
#- name: "i686-pc-windows-gnu"
# env: TARGET=i686-pc-windows-gnu CROSS=1
# Tier 2/3 targets:
- name: "i586-unknown-linux-gnu (no SSE2)"
env: TARGET=i586-unknown-linux-gnu CROSS=1
- name: "armv7-unknown-linux-gnueabihf"
env: TARGET=armv7-unknown-linux-gnueabihf CROSS=1
- name: "aarch64-unknown-linux-gnu"
env: TARGET=aarch64-unknown-linux-gnu CROSS=1
# Ensure that we successfully build without libstd
- name: "thumbv6m-none-eabi"
env: TARGET=thumbv6m-none-eabi NO_STD=1
script:
# cross doesn't seem to work with thumb targets...
- rustup target install $TARGET
- sh ci/run.sh
install: travis_retry rustup target add "${TARGET}"
script: sh ci/run.sh
branches:
# Don't build these branches
except:
# Used by bors
- trying.tmp
- staging.tmp
+2 -2
View File
@@ -2,8 +2,8 @@
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
The format is based on [Keep a Changelog](https://keepachangelog.com/)
and this project adheres to [Semantic Versioning](https://semver.org/).
## [Unreleased]
+1 -1
View File
@@ -8,7 +8,7 @@ repository = "https://github.com/rust-lang/hashbrown"
readme = "README.md"
keywords = ["hash", "no_std", "hashmap", "swisstable"]
categories = ["data-structures", "no-std"]
exclude = [".travis.yml", "bors.toml", "/ci/*"]
exclude = [".github", "bors.toml", "/ci/*"]
edition = "2018"
[dependencies]
+3 -3
View File
@@ -1,7 +1,7 @@
hashbrown
=========
[![Build Status](https://travis-ci.com/rust-lang/hashbrown.svg?branch=master)](https://travis-ci.com/rust-lang/hashbrown)
[![Build Status](https://github.com/rust-lang/hashbrown/actions/workflows/rust.yml/badge.svg)](https://github.com/rust-lang/hashbrown/actions)
[![Crates.io](https://img.shields.io/crates/v/hashbrown.svg)](https://crates.io/crates/hashbrown)
[![Documentation](https://docs.rs/hashbrown/badge.svg)](https://docs.rs/hashbrown)
[![Rust](https://img.shields.io/badge/rust-1.49.0%2B-blue.svg?maxAge=3600)](https://github.com/rust-lang/hashbrown)
@@ -114,8 +114,8 @@ this pre-generates seeds at compile time and embeds them as constants. See [aHas
Licensed under either of:
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)
at your option.
+1 -1
View File
@@ -38,7 +38,7 @@ impl Iterator for RandomKeys {
type Item = usize;
fn next(&mut self) -> Option<usize> {
// Add 1 then multiply by some 32 bit prime.
self.state = self.state.wrapping_add(1).wrapping_mul(3787392781);
self.state = self.state.wrapping_add(1).wrapping_mul(3_787_392_781);
Some(self.state)
}
}
+2 -2
View File
@@ -12,7 +12,7 @@ else
FEATURES="rustc-internal-api,serde,rayon,raw,bumpalo"
OP="test"
fi
if [ "${TRAVIS_RUST_VERSION}" = "nightly" ]; then
if [ "${CHANNEL}" = "nightly" ]; then
FEATURES="${FEATURES},nightly"
export RUSTFLAGS="$RUSTFLAGS -D warnings"
fi
@@ -36,7 +36,7 @@ fi
"${CARGO}" -vv ${OP} --target="${TARGET}" --release
"${CARGO}" -vv ${OP} --target="${TARGET}" --release --features "${FEATURES}"
if [ "${TRAVIS_RUST_VERSION}" = "nightly" ] && [ "${NO_STD}" != 1 ]; then
if [ "${CHANNEL}" = "nightly" ] && [ "${NO_STD}" != 1 ]; then
# Run benchmark on native targets, build them on non-native ones:
NO_RUN=""
if [ "${CROSS}" = "1" ]; then
+3 -7
View File
@@ -30,13 +30,9 @@ if retry rustup component add rustfmt ; then
fi
if retry rustup component add clippy ; then
cargo clippy --all -- -D clippy::pedantic
fi
if [ "${TRAVIS_OS_NAME}" = "linux" ]; then
if retry rustup component add clippy ; then
cargo clippy --all --target=i586-unknown-linux-gnu -- -D clippy::all -D clippy::pedantic
fi
cargo clippy --all --tests --features serde,rayon,bumpalo -- -D clippy::all -D clippy::pedantic
cargo clippy --all --tests --features raw -- -D clippy::all -D clippy::pedantic \
-A clippy::missing_safety_doc -A clippy::missing_errors_doc
fi
if command -v shellcheck ; then
@@ -4,6 +4,7 @@ use alloc::vec::Vec;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
/// Helper for collecting parallel iterators to an intermediary
#[allow(clippy::linkedlist)] // yes, we need linked list here for efficient appending!
pub(super) fn collect<I: IntoParallelIterator>(iter: I) -> (LinkedList<Vec<I::Item>>, usize) {
let list = iter
.into_par_iter()
+2 -2
View File
@@ -512,7 +512,7 @@ mod test_par_map {
where
H: Hasher,
{
self.k.hash(state)
self.k.hash(state);
}
}
@@ -679,7 +679,7 @@ mod test_par_map {
fn test_values_mut() {
let vec = vec![(1, 1), (2, 2), (3, 3)];
let mut map: HashMap<_, _> = vec.into_par_iter().collect();
map.par_values_mut().for_each(|value| *value = (*value) * 2);
map.par_values_mut().for_each(|value| *value *= 2);
let values: Vec<_> = map.par_values().cloned().collect();
assert_eq!(values.len(), 3);
assert!(values.contains(&2));
+6 -4
View File
@@ -134,7 +134,7 @@ impl<T: Send, A: Allocator + Clone> ParallelIterator for RawParDrain<'_, T, A> {
C: UnindexedConsumer<Self::Item>,
{
let _guard = guard(self.table, |table| unsafe {
table.as_mut().clear_no_drop()
table.as_mut().clear_no_drop();
});
let iter = unsafe { self.table.as_ref().iter().iter };
mem::forget(self);
@@ -146,7 +146,9 @@ impl<T: Send, A: Allocator + Clone> ParallelIterator for RawParDrain<'_, T, A> {
impl<T, A: Allocator + Clone> Drop for RawParDrain<'_, T, A> {
fn drop(&mut self) {
// If drive_unindexed is not called then simply clear the table.
unsafe { self.table.as_mut().clear() }
unsafe {
self.table.as_mut().clear();
}
}
}
@@ -175,7 +177,7 @@ impl<T: Send> UnindexedProducer for ParDrainProducer<T> {
{
// Make sure to modify the iterator in-place so that any remaining
// elements are processed in our Drop impl.
while let Some(item) = self.iter.next() {
for item in &mut self.iter {
folder = folder.consume(unsafe { item.read() });
if folder.full() {
return folder;
@@ -193,7 +195,7 @@ impl<T> Drop for ParDrainProducer<T> {
fn drop(&mut self) {
// Drop all remaining elements
if mem::needs_drop::<T>() {
while let Some(item) = self.iter.next() {
for item in &mut self.iter {
unsafe {
item.drop();
}
+1
View File
@@ -161,6 +161,7 @@ mod set {
deserializer.deserialize_seq(visitor)
}
#[allow(clippy::missing_errors_doc)]
fn deserialize_in_place<D>(deserializer: D, place: &mut Self) -> Result<(), D::Error>
where
D: Deserializer<'de>,
+2 -2
View File
@@ -57,8 +57,8 @@ macro_rules! cfg_if {
// default fn syntax for specialization changes in the future.
#[cfg(feature = "nightly")]
macro_rules! default_fn {
($($tt:tt)*) => {
default $($tt)*
(#[$($a:tt)*] $($tt:tt)*) => {
#[$($a)*] default $($tt)*
}
}
#[cfg(not(feature = "nightly"))]
+51 -63
View File
@@ -8,8 +8,6 @@ use core::hash::{BuildHasher, Hash};
use core::iter::{FromIterator, FusedIterator};
use core::marker::PhantomData;
use core::mem;
#[cfg(feature = "nightly")]
use core::mem::MaybeUninit;
use core::ops::Index;
/// Default hasher for `HashMap`.
@@ -1149,16 +1147,7 @@ where
K: Borrow<Q>,
Q: Hash + Eq,
{
let mut pairs = self.get_each_inner_mut(ks);
// TODO use `MaybeUninit::uninit_array` here instead once that's stable.
let mut out: [MaybeUninit<Result<&'_ mut V, UnavailableMutError>>; N] =
unsafe { MaybeUninit::uninit().assume_init() };
for i in 0..N {
out[i] = MaybeUninit::new(
mem::replace(&mut pairs[i], Err(UnavailableMutError::Absent)).map(|(_, v)| v),
);
}
unsafe { MaybeUninit::array_assume_init(out) }
self.get_each_inner_mut(ks).map(|res| res.map(|(_, v)| v))
}
/// Attempts to get mutable references to `N` values in the map at once, with immutable
@@ -1208,17 +1197,8 @@ where
K: Borrow<Q>,
Q: Hash + Eq,
{
let mut pairs = self.get_each_inner_mut(ks);
// TODO use `MaybeUninit::uninit_array` here instead once that's stable.
let mut out: [MaybeUninit<Result<(&'_ K, &'_ mut V), UnavailableMutError>>; N] =
unsafe { MaybeUninit::uninit().assume_init() };
for i in 0..N {
out[i] = MaybeUninit::new(
mem::replace(&mut pairs[i], Err(UnavailableMutError::Absent))
.map(|(k, v)| (&*k, v)),
);
}
unsafe { MaybeUninit::array_assume_init(out) }
self.get_each_inner_mut(ks)
.map(|res| res.map(|(k, v)| (&*k, v)))
}
#[cfg(feature = "nightly")]
@@ -1692,7 +1672,7 @@ pub(super) struct ConsumeAllOnDrop<'a, T: Iterator>(pub &'a mut T);
impl<T: Iterator> Drop for ConsumeAllOnDrop<'_, T> {
#[cfg_attr(feature = "inline-more", inline)]
fn drop(&mut self) {
self.0.for_each(drop)
self.0.for_each(drop);
}
}
@@ -1729,7 +1709,7 @@ impl<K, V, A: Allocator + Clone> DrainFilterInner<'_, K, V, A> {
F: FnMut(&K, &mut V) -> bool,
{
unsafe {
while let Some(item) = self.iter.next() {
for item in &mut self.iter {
let &mut (ref key, ref mut value) = item.as_mut();
if f(key, value) {
return Some(self.table.remove(item));
@@ -3507,7 +3487,6 @@ mod test_map {
use super::DefaultHashBuilder;
use super::Entry::{Occupied, Vacant};
use super::{HashMap, RawEntryMut};
use crate::TryReserveError::*;
use rand::{rngs::SmallRng, Rng, SeedableRng};
use std::borrow::ToOwned;
use std::cell::RefCell;
@@ -3576,6 +3555,7 @@ mod test_map {
assert_eq!(m.len(), 1);
assert!(m.insert(2, 4).is_none());
assert_eq!(m.len(), 2);
#[allow(clippy::redundant_clone)]
let m2 = m.clone();
assert_eq!(*m2.get(&1).unwrap(), 2);
assert_eq!(*m2.get(&2).unwrap(), 4);
@@ -3729,6 +3709,7 @@ mod test_map {
}
});
#[allow(clippy::let_underscore_drop)] // kind-of a false positive
for _ in half.by_ref() {}
DROP_VECTOR.with(|v| {
@@ -3940,7 +3921,7 @@ mod test_map {
fn test_keys() {
let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')];
let map: HashMap<_, _> = vec.into_iter().collect();
let keys: Vec<_> = map.keys().cloned().collect();
let keys: Vec<_> = map.keys().copied().collect();
assert_eq!(keys.len(), 3);
assert!(keys.contains(&1));
assert!(keys.contains(&2));
@@ -3951,7 +3932,7 @@ mod test_map {
fn test_values() {
let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')];
let map: HashMap<_, _> = vec.into_iter().collect();
let values: Vec<_> = map.values().cloned().collect();
let values: Vec<_> = map.values().copied().collect();
assert_eq!(values.len(), 3);
assert!(values.contains(&'a'));
assert!(values.contains(&'b'));
@@ -3963,9 +3944,9 @@ mod test_map {
let vec = vec![(1, 1), (2, 2), (3, 3)];
let mut map: HashMap<_, _> = vec.into_iter().collect();
for value in map.values_mut() {
*value = (*value) * 2
*value *= 2;
}
let values: Vec<_> = map.values().cloned().collect();
let values: Vec<_> = map.values().copied().collect();
assert_eq!(values.len(), 3);
assert!(values.contains(&2));
assert!(values.contains(&4));
@@ -4130,7 +4111,7 @@ mod test_map {
fn test_from_iter() {
let xs = [(1, 1), (2, 2), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)];
let map: HashMap<_, _> = xs.iter().cloned().collect();
let map: HashMap<_, _> = xs.iter().copied().collect();
for &(k, v) in &xs {
assert_eq!(map.get(&k), Some(&v));
@@ -4143,7 +4124,7 @@ mod test_map {
fn test_size_hint() {
let xs = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)];
let map: HashMap<_, _> = xs.iter().cloned().collect();
let map: HashMap<_, _> = xs.iter().copied().collect();
let mut iter = map.iter();
@@ -4156,7 +4137,7 @@ mod test_map {
fn test_iter_len() {
let xs = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)];
let map: HashMap<_, _> = xs.iter().cloned().collect();
let map: HashMap<_, _> = xs.iter().copied().collect();
let mut iter = map.iter();
@@ -4169,7 +4150,7 @@ mod test_map {
fn test_mut_size_hint() {
let xs = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)];
let mut map: HashMap<_, _> = xs.iter().cloned().collect();
let mut map: HashMap<_, _> = xs.iter().copied().collect();
let mut iter = map.iter_mut();
@@ -4182,7 +4163,7 @@ mod test_map {
fn test_iter_mut_len() {
let xs = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)];
let mut map: HashMap<_, _> = xs.iter().cloned().collect();
let mut map: HashMap<_, _> = xs.iter().copied().collect();
let mut iter = map.iter_mut();
@@ -4211,6 +4192,7 @@ mod test_map {
map.insert(2, 1);
map.insert(3, 4);
#[allow(clippy::no_effect)] // false positive lint
map[&4];
}
@@ -4218,7 +4200,7 @@ mod test_map {
fn test_entry() {
let xs = [(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)];
let mut map: HashMap<_, _> = xs.iter().cloned().collect();
let mut map: HashMap<_, _> = xs.iter().copied().collect();
// Existing key (insert)
match map.entry(1) {
@@ -4350,11 +4332,11 @@ mod test_map {
let key = "hello there";
let value = "value goes here";
assert!(a.is_empty());
a.insert(key.clone(), value.clone());
a.insert(key, value);
assert_eq!(a.len(), 1);
assert_eq!(a[key], value);
match a.entry(key.clone()) {
match a.entry(key) {
Vacant(_) => panic!(),
Occupied(e) => assert_eq!(key, *e.key()),
}
@@ -4369,11 +4351,11 @@ mod test_map {
let value = "value goes here";
assert!(a.is_empty());
match a.entry(key.clone()) {
match a.entry(key) {
Occupied(_) => panic!(),
Vacant(e) => {
assert_eq!(key, *e.key());
e.insert(value.clone());
e.insert(value);
}
}
assert_eq!(a.len(), 1);
@@ -4641,21 +4623,27 @@ mod test_map {
#[test]
#[cfg_attr(miri, ignore)] // FIXME: no OOM signalling (https://github.com/rust-lang/miri/issues/613)
fn test_try_reserve() {
let mut empty_bytes: HashMap<u8, u8> = HashMap::new();
use crate::TryReserveError::{AllocError, CapacityOverflow};
const MAX_USIZE: usize = usize::MAX;
let mut empty_bytes: HashMap<u8, u8> = HashMap::new();
if let Err(CapacityOverflow) = empty_bytes.try_reserve(MAX_USIZE) {
} else {
panic!("usize::MAX should trigger an overflow!");
}
if let Err(AllocError { .. }) = empty_bytes.try_reserve(MAX_USIZE / 8) {
if let Err(AllocError { .. }) = empty_bytes.try_reserve(MAX_USIZE / 16) {
} else {
// This may succeed if there is enough free memory. Attempt to
// allocate a second hashmap to ensure the allocation will fail.
// allocate a few more hashmaps to ensure the allocation will fail.
let mut empty_bytes2: HashMap<u8, u8> = HashMap::new();
if let Err(AllocError { .. }) = empty_bytes2.try_reserve(MAX_USIZE / 8) {
let _ = empty_bytes2.try_reserve(MAX_USIZE / 16);
let mut empty_bytes3: HashMap<u8, u8> = HashMap::new();
let _ = empty_bytes3.try_reserve(MAX_USIZE / 16);
let mut empty_bytes4: HashMap<u8, u8> = HashMap::new();
if let Err(AllocError { .. }) = empty_bytes4.try_reserve(MAX_USIZE / 16) {
} else {
panic!("usize::MAX / 8 should trigger an OOM!");
}
@@ -4666,9 +4654,9 @@ mod test_map {
fn test_raw_entry() {
use super::RawEntryMut::{Occupied, Vacant};
let xs = [(1i32, 10i32), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)];
let xs = [(1_i32, 10_i32), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)];
let mut map: HashMap<_, _> = xs.iter().cloned().collect();
let mut map: HashMap<_, _> = xs.iter().copied().collect();
let compute_hash = |map: &HashMap<i32, i32>, k: i32| -> u64 {
super::make_insert_hash::<i32, _>(map.hasher(), &k)
@@ -4741,7 +4729,7 @@ mod test_map {
// Ensure all lookup methods produce equivalent results.
for k in 0..12 {
let hash = compute_hash(&map, k);
let v = map.get(&k).cloned();
let v = map.get(&k).copied();
let kv = v.as_ref().map(|v| (&k, v));
assert_eq!(map.raw_entry().from_key(&k), kv);
@@ -4820,13 +4808,13 @@ mod test_map {
let mut rng = rand::thread_rng();
for n in 0..N {
let mut m = HashMap::new();
let mut map = HashMap::new();
for i in 0..n {
assert!(m.insert(i, 2 * i).is_none());
assert!(map.insert(i, 2 * i).is_none());
}
let hasher = m.hasher().clone();
let hash_builder = map.hasher().clone();
let mut it = unsafe { m.table.iter() };
let mut it = unsafe { map.table.iter() };
assert_eq!(it.len(), n);
let mut i = 0;
@@ -4835,23 +4823,23 @@ mod test_map {
loop {
// occasionally remove some elements
if i < n && rng.gen_bool(0.1) {
let mut hsh = hasher.build_hasher();
i.hash(&mut hsh);
let hash = hsh.finish();
let mut hasher = hash_builder.build_hasher();
i.hash(&mut hasher);
let hash_value = hasher.finish();
unsafe {
let e = m.table.find(hash, |q| q.0.eq(&i));
let e = map.table.find(hash_value, |q| q.0.eq(&i));
if let Some(e) = e {
it.reflect_remove(&e);
let t = m.table.remove(e);
let t = map.table.remove(e);
removed.push(t);
left -= 1;
} else {
assert!(removed.contains(&(i, 2 * i)), "{} not in {:?}", i, removed);
let e = m.table.insert(
hash,
let e = map.table.insert(
hash_value,
(i, 2 * i),
super::make_hasher::<usize, _, usize, _>(&hasher),
super::make_hasher::<usize, _, usize, _>(&hash_builder),
);
it.reflect_insert(&e);
if let Some(p) = removed.iter().position(|e| e == &(i, 2 * i)) {
@@ -4869,14 +4857,14 @@ mod test_map {
assert!(i < n);
let t = unsafe { e.unwrap().as_ref() };
assert!(!removed.contains(t));
let (k, v) = t;
assert_eq!(*v, 2 * k);
let (key, value) = t;
assert_eq!(*value, 2 * key);
i += 1;
}
assert!(i <= n);
// just for safety:
assert_eq!(m.table.len(), left);
assert_eq!(map.table.len(), left);
}
}
@@ -4898,7 +4886,7 @@ mod test_map {
const EMPTY_MAP: HashMap<u32, std::string::String, MyHasher> =
HashMap::with_hasher(MyHasher);
let mut map = EMPTY_MAP.clone();
let mut map = EMPTY_MAP;
map.insert(17, "seventeen".to_owned());
assert_eq!("seventeen", map[&17]);
}
+1 -1
View File
@@ -47,7 +47,7 @@ mod inner {
}
#[inline]
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
dealloc(ptr.as_ptr(), layout)
dealloc(ptr.as_ptr(), layout);
}
}
impl Default for Global {
+1 -1
View File
@@ -106,7 +106,7 @@ impl IntoIterator for BitMask {
}
}
/// Iterator over the contents of a `BitMask`, returning the indicies of set
/// Iterator over the contents of a `BitMask`, returning the indices of set
/// bits.
pub struct BitMaskIter(BitMask);
+5 -2
View File
@@ -9,12 +9,14 @@ use core::{mem, ptr};
target_pointer_width = "64",
target_arch = "aarch64",
target_arch = "x86_64",
target_arch = "wasm32",
))]
type GroupWord = u64;
#[cfg(all(
target_pointer_width = "32",
not(target_arch = "aarch64"),
not(target_arch = "x86_64"),
not(target_arch = "wasm32"),
))]
type GroupWord = u32;
@@ -37,7 +39,7 @@ fn repeat(byte: u8) -> GroupWord {
#[derive(Copy, Clone)]
pub struct Group(GroupWord);
// We perform all operations in the native endianess, and convert to
// We perform all operations in the native endianness, and convert to
// little-endian just before creating a BitMask. The can potentially
// enable the compiler to eliminate unnecessary byte swaps if we are
// only checking whether a BitMask is empty.
@@ -50,6 +52,7 @@ impl Group {
/// value for an empty hash table.
///
/// This is guaranteed to be aligned to the group size.
#[inline]
pub const fn static_empty() -> &'static [u8; Group::WIDTH] {
#[repr(C)]
struct AlignedBytes {
@@ -103,7 +106,7 @@ impl Group {
#[inline]
pub fn match_byte(self, byte: u8) -> BitMask {
// This algorithm is derived from
// http://graphics.stanford.edu/~seander/bithacks.html##ValueInWord
// https://graphics.stanford.edu/~seander/bithacks.html##ValueInWord
let cmp = self.0 ^ repeat(byte);
BitMask((cmp.wrapping_sub(repeat(0x01)) & !cmp & repeat(0x80)).to_le())
}
+321 -177
View File
@@ -3,7 +3,6 @@ use crate::scopeguard::guard;
use crate::TryReserveError;
#[cfg(feature = "nightly")]
use crate::UnavailableMutError;
use core::hint;
use core::iter::FusedIterator;
use core::marker::PhantomData;
use core::mem;
@@ -11,6 +10,7 @@ use core::mem::ManuallyDrop;
#[cfg(feature = "nightly")]
use core::mem::MaybeUninit;
use core::ptr::NonNull;
use core::{hint, ptr};
cfg_if! {
// Use the SSE2 implementation if possible: it allows us to scan 16 buckets
@@ -59,7 +59,7 @@ fn cold() {}
#[inline]
fn likely(b: bool) -> bool {
if !b {
cold()
cold();
}
b
}
@@ -67,18 +67,18 @@ fn likely(b: bool) -> bool {
#[inline]
fn unlikely(b: bool) -> bool {
if b {
cold()
cold();
}
b
}
#[cfg(feature = "nightly")]
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
unsafe fn offset_from<T>(to: *const T, from: *const T) -> usize {
to.offset_from(from) as usize
}
#[cfg(not(feature = "nightly"))]
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
unsafe fn offset_from<T>(to: *const T, from: *const T) -> usize {
(to as usize - from as usize) / mem::size_of::<T>()
}
@@ -211,7 +211,7 @@ fn capacity_to_buckets(cap: usize) -> Option<usize> {
// Any overflows will have been caught by the checked_mul. Also, any
// rounding errors from the division above will be cleaned up by
// next_power_of_two (which can't overflow because of the previous divison).
// next_power_of_two (which can't overflow because of the previous division).
Some(adjusted_cap.next_power_of_two())
}
@@ -292,14 +292,14 @@ pub struct Bucket<T> {
unsafe impl<T> Send for Bucket<T> {}
impl<T> Clone for Bucket<T> {
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
fn clone(&self) -> Self {
Self { ptr: self.ptr }
}
}
impl<T> Bucket<T> {
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
unsafe fn from_base_index(base: NonNull<T>, index: usize) -> Self {
let ptr = if mem::size_of::<T>() == 0 {
// won't overflow because index must be less than length
@@ -311,7 +311,7 @@ impl<T> Bucket<T> {
ptr: NonNull::new_unchecked(ptr),
}
}
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
unsafe fn to_base_index(&self, base: NonNull<T>) -> usize {
if mem::size_of::<T>() == 0 {
self.ptr.as_ptr() as usize - 1
@@ -319,7 +319,7 @@ impl<T> Bucket<T> {
offset_from(base.as_ptr(), self.ptr.as_ptr())
}
}
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
pub fn as_ptr(&self) -> *mut T {
if mem::size_of::<T>() == 0 {
// Just return an arbitrary ZST pointer which is properly aligned
@@ -328,7 +328,7 @@ impl<T> Bucket<T> {
unsafe { self.ptr.as_ptr().sub(1) }
}
}
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
unsafe fn next_n(&self, offset: usize) -> Self {
let ptr = if mem::size_of::<T>() == 0 {
(self.ptr.as_ptr() as usize + offset) as *mut T
@@ -343,23 +343,24 @@ impl<T> Bucket<T> {
pub unsafe fn drop(&self) {
self.as_ptr().drop_in_place();
}
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
pub unsafe fn read(&self) -> T {
self.as_ptr().read()
}
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
pub unsafe fn write(&self, val: T) {
self.as_ptr().write(val);
}
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
pub unsafe fn as_ref<'a>(&self) -> &'a T {
&*self.as_ptr()
}
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
pub unsafe fn as_mut<'a>(&self) -> &'a mut T {
&mut *self.as_ptr()
}
#[cfg_attr(feature = "inline-more", inline)]
#[cfg(feature = "raw")]
#[inline]
pub unsafe fn copy_from_nonoverlapping(&self, other: &Self) {
self.as_ptr().copy_from_nonoverlapping(other.as_ptr(), 1);
}
@@ -398,7 +399,7 @@ impl<T> RawTable<T, Global> {
/// In effect this returns a table with exactly 1 bucket. However we can
/// leave the data pointer dangling since that bucket is never written to
/// due to our load factor forcing us to always have at least 1 free bucket.
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
pub const fn new() -> Self {
Self {
table: RawTableInner::new_in(Global),
@@ -427,7 +428,7 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
/// In effect this returns a table with exactly 1 bucket. However we can
/// leave the data pointer dangling since that bucket is never written to
/// due to our load factor forcing us to always have at least 1 free bucket.
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
pub fn new_in(alloc: A) -> Self {
Self {
table: RawTableInner::new_in(alloc),
@@ -501,30 +502,30 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
/// Deallocates the table without dropping any entries.
#[cfg_attr(feature = "inline-more", inline)]
unsafe fn free_buckets(&mut self) {
self.table.free_buckets(TableLayout::new::<T>())
self.table.free_buckets(TableLayout::new::<T>());
}
/// Returns pointer to one past last element of data table.
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
pub unsafe fn data_end(&self) -> NonNull<T> {
NonNull::new_unchecked(self.table.ctrl.as_ptr().cast())
}
/// Returns pointer to start of data table.
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
#[cfg(feature = "nightly")]
pub unsafe fn data_start(&self) -> *mut T {
self.data_end().as_ptr().wrapping_sub(self.buckets())
}
/// Returns the index of a bucket from a `Bucket`.
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
pub unsafe fn bucket_index(&self, bucket: &Bucket<T>) -> usize {
bucket.to_base_index(self.data_end())
}
/// Returns a pointer to an element in the table.
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
pub unsafe fn bucket(&self, index: usize) -> Bucket<T> {
debug_assert_ne!(self.table.bucket_mask, 0);
debug_assert!(index < self.buckets());
@@ -536,7 +537,7 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
#[deprecated(since = "0.8.1", note = "use erase or remove instead")]
pub unsafe fn erase_no_drop(&mut self, item: &Bucket<T>) {
let index = self.bucket_index(item);
self.table.erase(index)
self.table.erase(index);
}
/// Erases an element from the table, dropping it in place.
@@ -556,7 +557,9 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
pub fn erase_entry(&mut self, hash: u64, eq: impl FnMut(&T) -> bool) -> bool {
// Avoid `Option::map` because it bloats LLVM IR.
if let Some(bucket) = self.find(hash, eq) {
unsafe { self.erase(bucket) };
unsafe {
self.erase(bucket);
}
true
} else {
false
@@ -585,7 +588,7 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
/// Marks all table buckets as empty without dropping their contents.
#[cfg_attr(feature = "inline-more", inline)]
pub fn clear_no_drop(&mut self) {
self.table.clear_no_drop()
self.table.clear_no_drop();
}
/// Removes all elements from the table without freeing the backing memory.
@@ -599,7 +602,7 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
}
unsafe fn drop_elements(&mut self) {
if mem::needs_drop::<T>() && self.len() != 0 {
if mem::needs_drop::<T>() && !self.is_empty() {
for item in self.iter() {
item.drop();
}
@@ -630,7 +633,7 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
if min_buckets < self.buckets() {
// Fast path if the table is empty
if self.table.items == 0 {
*self = Self::with_capacity_in(min_size, self.table.alloc.clone())
*self = Self::with_capacity_in(min_size, self.table.alloc.clone());
} else {
// Avoid `Result::unwrap_or_else` because it bloats LLVM IR.
if self
@@ -682,102 +685,18 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
hasher: impl Fn(&T) -> u64,
fallibility: Fallibility,
) -> Result<(), TryReserveError> {
// Avoid `Option::ok_or_else` because it bloats LLVM IR.
let new_items = match self.table.items.checked_add(additional) {
Some(new_items) => new_items,
None => return Err(fallibility.capacity_overflow()),
};
let full_capacity = bucket_mask_to_capacity(self.table.bucket_mask);
if new_items <= full_capacity / 2 {
// Rehash in-place without re-allocating if we have plenty of spare
// capacity that is locked up due to DELETED entries.
self.rehash_in_place(hasher);
Ok(())
} else {
// Otherwise, conservatively resize to at least the next size up
// to avoid churning deletes into frequent rehashes.
self.resize(
usize::max(new_items, full_capacity + 1),
hasher,
fallibility,
)
}
}
/// Rehashes the contents of the table in place (i.e. without changing the
/// allocation).
///
/// If `hasher` panics then some the table's contents may be lost.
fn rehash_in_place(&mut self, hasher: impl Fn(&T) -> u64) {
unsafe {
// If the hash function panics then properly clean up any elements
// that we haven't rehashed yet. We unfortunately can't preserve the
// element since we lost their hash and have no way of recovering it
// without risking another panic.
self.table.prepare_rehash_in_place();
let mut guard = guard(&mut self.table, move |self_| {
self.table.reserve_rehash_inner(
additional,
&|table, index| hasher(table.bucket::<T>(index).as_ref()),
fallibility,
TableLayout::new::<T>(),
if mem::needs_drop::<T>() {
for i in 0..self_.buckets() {
if *self_.ctrl(i) == DELETED {
self_.set_ctrl(i, EMPTY);
self_.bucket::<T>(i).drop();
self_.items -= 1;
}
}
}
self_.growth_left = bucket_mask_to_capacity(self_.bucket_mask) - self_.items;
});
// At this point, DELETED elements are elements that we haven't
// rehashed yet. Find them and re-insert them at their ideal
// position.
'outer: for i in 0..guard.buckets() {
if *guard.ctrl(i) != DELETED {
continue;
}
'inner: loop {
// Hash the current item
let item = guard.bucket(i);
let hash = hasher(item.as_ref());
// Search for a suitable place to put it
let new_i = guard.find_insert_slot(hash);
// Probing works by scanning through all of the control
// bytes in groups, which may not be aligned to the group
// size. If both the new and old position fall within the
// same unaligned group, then there is no benefit in moving
// it and we can just continue to the next item.
if likely(guard.is_in_same_group(i, new_i, hash)) {
guard.set_ctrl_h2(i, hash);
continue 'outer;
}
// We are moving the current item to a new position. Write
// our H2 to the control byte of the new position.
let prev_ctrl = guard.replace_ctrl_h2(new_i, hash);
if prev_ctrl == EMPTY {
guard.set_ctrl(i, EMPTY);
// If the target slot is empty, simply move the current
// element into the new slot and clear the old control
// byte.
guard.bucket(new_i).copy_from_nonoverlapping(&item);
continue 'outer;
} else {
// If the target slot is occupied, swap the two elements
// and then continue processing the element that we just
// swapped into the old slot.
debug_assert_eq!(prev_ctrl, DELETED);
mem::swap(guard.bucket(new_i).as_mut(), item.as_mut());
continue 'inner;
}
}
}
guard.growth_left = bucket_mask_to_capacity(guard.bucket_mask) - guard.items;
mem::forget(guard);
Some(mem::transmute(ptr::drop_in_place::<T> as unsafe fn(*mut T)))
} else {
None
},
)
}
}
@@ -790,30 +709,12 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
fallibility: Fallibility,
) -> Result<(), TryReserveError> {
unsafe {
let mut new_table =
self.table
.prepare_resize(TableLayout::new::<T>(), capacity, fallibility)?;
// Copy all elements to the new table.
for item in self.iter() {
// 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 know there is enough space in the table.
// - all elements are unique.
let (index, _) = new_table.prepare_insert_slot(hash);
new_table.bucket(index).copy_from_nonoverlapping(&item);
}
// We successfully copied all elements without panicking. Now replace
// self with the new table. The old table will have its memory freed but
// the items will not be dropped (since they have been moved into the
// new table).
mem::swap(&mut self.table, &mut new_table);
Ok(())
self.table.resize_inner(
capacity,
&|table, index| hasher(table.bucket::<T>(index).as_ref()),
fallibility,
TableLayout::new::<T>(),
)
}
}
@@ -921,14 +822,14 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
/// Searches for an element in the table.
#[inline]
pub fn find(&self, hash: u64, mut eq: impl FnMut(&T) -> bool) -> Option<Bucket<T>> {
unsafe {
for bucket in self.iter_hash(hash) {
let elm = bucket.as_ref();
if likely(eq(elm)) {
return Some(bucket);
}
}
None
let result = self.table.find_inner(hash, &mut |index| unsafe {
eq(self.bucket(index).as_ref())
});
// Avoid `Option::map` because it bloats LLVM IR.
match result {
Some(index) => Some(unsafe { self.bucket(index) }),
None => None,
}
}
@@ -1014,19 +915,25 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
///
/// This number is a lower bound; the table might be able to hold
/// more, but is guaranteed to be able to hold at least this many.
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
pub fn capacity(&self) -> usize {
self.table.items + self.table.growth_left
}
/// Returns the number of elements in the table.
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
pub fn len(&self) -> usize {
self.table.items
}
/// Returns `true` if the table contains no elements.
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the number of buckets in the table.
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
pub fn buckets(&self) -> usize {
self.table.bucket_mask + 1
}
@@ -1035,7 +942,7 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
/// the caller to ensure that the `RawTable` outlives the `RawIter`.
/// Because we cannot make the `next` method unsafe on the `RawIter`
/// struct, we have to make the `iter` method unsafe.
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
pub unsafe fn iter(&self) -> RawIter<T> {
let data = Bucket::from_base_index(self.data_end(), 0);
RawIter {
@@ -1046,12 +953,15 @@ impl<T, A: Allocator + Clone> RawTable<T, A> {
/// Returns an iterator over occupied buckets that could match a given hash.
///
/// In rare cases, the iterator may return a bucket with a different hash.
/// `RawTable` only stores 7 bits of the hash value, so this iterator may
/// return items that have a hash value different than the one provided. You
/// should always validate the returned values before using them.
///
/// It is up to the caller to ensure that the `RawTable` outlives the
/// `RawIterHash`. Because we cannot make the `next` method unsafe on the
/// `RawIterHash` struct, we have to make the `iter_hash` method unsafe.
#[cfg_attr(feature = "inline-more", inline)]
#[cfg(feature = "raw")]
pub unsafe fn iter_hash(&self, hash: u64) -> RawIterHash<'_, T, A> {
RawIterHash::new(self, hash)
}
@@ -1129,7 +1039,7 @@ unsafe impl<T, A: Allocator + Clone> Send for RawTable<T, A> where T: Send {}
unsafe impl<T, A: Allocator + Clone> Sync for RawTable<T, A> where T: Sync {}
impl<A> RawTableInner<A> {
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
const fn new_in(alloc: A) -> Self {
Self {
// Be careful to cast the entire slice to a raw pointer.
@@ -1158,6 +1068,15 @@ impl<A: Allocator + Clone> RawTableInner<A> {
None => return Err(fallibility.capacity_overflow()),
};
// We need an additional check to ensure that the allocation doesn't
// exceed `isize::MAX`. We can skip this check on 64-bit systems since
// such allocations will never succeed anyways.
//
// This mirrors what Vec does in the standard library.
if mem::size_of::<usize>() < 8 && layout.size() > isize::MAX as usize {
return Err(fallibility.capacity_overflow());
}
let ptr: NonNull<u8> = match do_alloc(&alloc, layout) {
Ok(block) => block.cast(),
Err(_) => return Err(fallibility.alloc_err(layout)),
@@ -1225,7 +1144,7 @@ impl<A: Allocator + Clone> RawTableInner<A> {
// EMPTY entries. These will unfortunately trigger a
// match, but once masked may point to a full bucket that
// is already occupied. We detect this situation here and
// perform a second scan starting at the begining of the
// perform a second scan starting at the beginning of the
// table. This second scan is guaranteed to find an empty
// slot (due to the load factor) before hitting the trailing
// control bytes (containing EMPTY).
@@ -1244,6 +1163,32 @@ impl<A: Allocator + Clone> RawTableInner<A> {
}
}
/// Searches for an element in the table. This uses dynamic dispatch to reduce the amount of
/// code generated, but it is eliminated by LLVM optimizations.
#[inline]
fn find_inner(&self, hash: u64, eq: &mut dyn FnMut(usize) -> bool) -> Option<usize> {
let h2_hash = h2(hash);
let mut probe_seq = self.probe_seq(hash);
loop {
let group = unsafe { Group::load(self.ctrl(probe_seq.pos)) };
for bit in group.match_byte(h2_hash) {
let index = (probe_seq.pos + bit) & self.bucket_mask;
if likely(eq(index)) {
return Some(index);
}
}
if likely(group.match_empty().any_bit_set()) {
return None;
}
probe_seq.move_next(self.bucket_mask);
}
}
#[allow(clippy::mut_mut)]
#[inline]
unsafe fn prepare_rehash_in_place(&mut self) {
@@ -1267,14 +1212,22 @@ impl<A: Allocator + Clone> RawTableInner<A> {
}
}
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
unsafe fn bucket<T>(&self, index: usize) -> Bucket<T> {
debug_assert_ne!(self.bucket_mask, 0);
debug_assert!(index < self.buckets());
Bucket::from_base_index(self.data_end(), index)
}
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
unsafe fn bucket_ptr(&self, index: usize, size_of: usize) -> *mut u8 {
debug_assert_ne!(self.bucket_mask, 0);
debug_assert!(index < self.buckets());
let base: *mut u8 = self.data_end().as_ptr();
base.sub((index + 1) * size_of)
}
#[inline]
unsafe fn data_end<T>(&self) -> NonNull<T> {
NonNull::new_unchecked(self.ctrl.as_ptr().cast())
}
@@ -1326,7 +1279,7 @@ impl<A: Allocator + Clone> RawTableInner<A> {
/// the end of the array.
#[inline]
unsafe fn set_ctrl_h2(&self, index: usize, hash: u64) {
self.set_ctrl(index, h2(hash))
self.set_ctrl(index, h2(hash));
}
#[inline]
@@ -1419,6 +1372,179 @@ impl<A: Allocator + Clone> RawTableInner<A> {
}))
}
/// Reserves or rehashes to make room for `additional` more elements.
///
/// This uses dynamic dispatch to reduce the amount of
/// code generated, but it is eliminated by LLVM optimizations when inlined.
#[allow(clippy::inline_always)]
#[inline(always)]
unsafe fn reserve_rehash_inner(
&mut self,
additional: usize,
hasher: &dyn Fn(&mut Self, usize) -> u64,
fallibility: Fallibility,
layout: TableLayout,
drop: Option<fn(*mut u8)>,
) -> Result<(), TryReserveError> {
// Avoid `Option::ok_or_else` because it bloats LLVM IR.
let new_items = match self.items.checked_add(additional) {
Some(new_items) => new_items,
None => return Err(fallibility.capacity_overflow()),
};
let full_capacity = bucket_mask_to_capacity(self.bucket_mask);
if new_items <= full_capacity / 2 {
// Rehash in-place without re-allocating if we have plenty of spare
// capacity that is locked up due to DELETED entries.
self.rehash_in_place(hasher, layout.size, drop);
Ok(())
} else {
// Otherwise, conservatively resize to at least the next size up
// to avoid churning deletes into frequent rehashes.
self.resize_inner(
usize::max(new_items, full_capacity + 1),
hasher,
fallibility,
layout,
)
}
}
/// Allocates a new table of a different size and moves the contents of the
/// current table into it.
///
/// This uses dynamic dispatch to reduce the amount of
/// code generated, but it is eliminated by LLVM optimizations when inlined.
#[allow(clippy::inline_always)]
#[inline(always)]
unsafe fn resize_inner(
&mut self,
capacity: usize,
hasher: &dyn Fn(&mut Self, usize) -> u64,
fallibility: Fallibility,
layout: TableLayout,
) -> Result<(), TryReserveError> {
let mut new_table = self.prepare_resize(layout, capacity, fallibility)?;
// Copy all elements to the new table.
for i in 0..self.buckets() {
if !is_full(*self.ctrl(i)) {
continue;
}
// This may panic.
let hash = hasher(self, i);
// 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.prepare_insert_slot(hash);
ptr::copy_nonoverlapping(
self.bucket_ptr(i, layout.size),
new_table.bucket_ptr(index, layout.size),
layout.size,
);
}
// We successfully copied all elements without panicking. Now replace
// self with the new table. The old table will have its memory freed but
// the items will not be dropped (since they have been moved into the
// new table).
mem::swap(self, &mut new_table);
Ok(())
}
/// Rehashes the contents of the table in place (i.e. without changing the
/// allocation).
///
/// If `hasher` panics then some the table's contents may be lost.
///
/// This uses dynamic dispatch to reduce the amount of
/// code generated, but it is eliminated by LLVM optimizations when inlined.
#[allow(clippy::inline_always)]
#[cfg_attr(feature = "inline-more", inline(always))]
#[cfg_attr(not(feature = "inline-more"), inline)]
unsafe fn rehash_in_place(
&mut self,
hasher: &dyn Fn(&mut Self, usize) -> u64,
size_of: usize,
drop: Option<fn(*mut u8)>,
) {
// If the hash function panics then properly clean up any elements
// that we haven't rehashed yet. We unfortunately can't preserve the
// element since we lost their hash and have no way of recovering it
// without risking another panic.
self.prepare_rehash_in_place();
let mut guard = guard(self, move |self_| {
if let Some(drop) = drop {
for i in 0..self_.buckets() {
if *self_.ctrl(i) == DELETED {
self_.set_ctrl(i, EMPTY);
drop(self_.bucket_ptr(i, size_of));
self_.items -= 1;
}
}
}
self_.growth_left = bucket_mask_to_capacity(self_.bucket_mask) - self_.items;
});
// At this point, DELETED elements are elements that we haven't
// rehashed yet. Find them and re-insert them at their ideal
// position.
'outer: for i in 0..guard.buckets() {
if *guard.ctrl(i) != DELETED {
continue;
}
let i_p = guard.bucket_ptr(i, size_of);
'inner: loop {
// Hash the current item
let hash = hasher(*guard, i);
// Search for a suitable place to put it
let new_i = guard.find_insert_slot(hash);
let new_i_p = guard.bucket_ptr(new_i, size_of);
// Probing works by scanning through all of the control
// bytes in groups, which may not be aligned to the group
// size. If both the new and old position fall within the
// same unaligned group, then there is no benefit in moving
// it and we can just continue to the next item.
if likely(guard.is_in_same_group(i, new_i, hash)) {
guard.set_ctrl_h2(i, hash);
continue 'outer;
}
// We are moving the current item to a new position. Write
// our H2 to the control byte of the new position.
let prev_ctrl = guard.replace_ctrl_h2(new_i, hash);
if prev_ctrl == EMPTY {
guard.set_ctrl(i, EMPTY);
// If the target slot is empty, simply move the current
// element into the new slot and clear the old control
// byte.
ptr::copy_nonoverlapping(i_p, new_i_p, size_of);
continue 'outer;
} else {
// If the target slot is occupied, swap the two elements
// and then continue processing the element that we just
// swapped into the old slot.
debug_assert_eq!(prev_ctrl, DELETED);
ptr::swap_nonoverlapping(i_p, new_i_p, size_of);
continue 'inner;
}
}
}
guard.growth_left = bucket_mask_to_capacity(guard.bucket_mask) - guard.items;
mem::forget(guard);
}
#[inline]
unsafe fn free_buckets(&mut self, table_layout: TableLayout) {
// Avoid `Option::unwrap_or_else` because it bloats LLVM IR.
@@ -1458,7 +1584,7 @@ impl<A: Allocator + Clone> RawTableInner<A> {
//
// Note that in this context `leading_zeros` refers to the bytes at the
// end of a group, while `trailing_zeros` refers to the bytes at the
// begining of a group.
// beginning of a group.
let ctrl = if empty_before.leading_zeros() + empty_after.trailing_zeros() >= Group::WIDTH {
DELETED
} else {
@@ -1528,7 +1654,7 @@ impl<T: Clone, A: Allocator + Clone> Clone for RawTable<T, A> {
self.clone_from_spec(source, |self_| {
// We need to leave the table in an empty state.
self_.clear_no_drop()
self_.clear_no_drop();
});
}
}
@@ -1540,8 +1666,8 @@ trait RawTableClone {
unsafe fn clone_from_spec(&mut self, source: &Self, on_panic: impl FnMut(&mut Self));
}
impl<T: Clone, A: Allocator + Clone> RawTableClone for RawTable<T, A> {
#[cfg_attr(feature = "inline-more", inline)]
default_fn! {
#[cfg_attr(feature = "inline-more", inline)]
unsafe fn clone_from_spec(&mut self, source: &Self, on_panic: impl FnMut(&mut Self)) {
self.clone_from_impl(source, on_panic);
}
@@ -1578,7 +1704,7 @@ impl<T: Clone, A: Allocator + Clone> RawTable<T, A> {
// to make sure we drop only the elements that have been
// cloned so far.
let mut guard = guard((0, &mut *self), |(index, self_)| {
if mem::needs_drop::<T>() && self_.len() != 0 {
if mem::needs_drop::<T>() && !self_.is_empty() {
for i in 0..=*index {
if is_full(*self_.table.ctrl(i)) {
self_.bucket(i).drop();
@@ -1654,7 +1780,7 @@ impl<T: Clone, A: Allocator + Clone> RawTable<T, A> {
}
impl<T, A: Allocator + Clone + Default> Default for RawTable<T, A> {
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
fn default() -> Self {
Self::new_in(Default::default())
}
@@ -1828,7 +1954,7 @@ impl<T> Iterator for RawIterRange<T> {
}
}
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
// We don't have an item count, so just guess based on the range size.
(
@@ -1875,7 +2001,7 @@ impl<T> RawIter<T> {
/// For the iterator to remain valid, this method must be called once
/// for each insert before `next` is called again.
///
/// This method does not guarantee that an insertion of a bucket witha greater
/// This method does not guarantee that an insertion of a bucket with a greater
/// index than the last one yielded will be reflected in the iterator.
///
/// This method should be called _after_ the given insert is made.
@@ -1927,7 +2053,7 @@ impl<T> RawIter<T> {
// If it did, we're done.
// - Otherwise, update the iterator cached group so that it won't
// yield a to-be-removed bucket, or _will_ yield a to-be-added bucket.
// We'll also need ot update the item count accordingly.
// We'll also need to update the item count accordingly.
if let Some(index) = self.iter.current_group.lowest_set_bit() {
let next_bucket = self.iter.data.next_n(index);
if b.as_ptr() > next_bucket.as_ptr() {
@@ -2010,7 +2136,7 @@ impl<T> Iterator for RawIter<T> {
}
}
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.items, Some(self.items))
}
@@ -2076,7 +2202,7 @@ impl<T, A: Allocator + Clone> Iterator for RawIntoIter<T, A> {
unsafe { Some(self.iter.next()?.read()) }
}
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
@@ -2140,7 +2266,7 @@ impl<T, A: Allocator + Clone> Iterator for RawDrain<'_, T, A> {
}
}
#[cfg_attr(feature = "inline-more", inline)]
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
@@ -2151,7 +2277,9 @@ impl<T, A: Allocator + Clone> FusedIterator for RawDrain<'_, T, A> {}
/// Iterator over occupied buckets that could match a given hash.
///
/// In rare cases, the iterator may return a bucket with a different hash.
/// `RawTable` only stores 7 bits of the hash value, so this iterator may return
/// items that have a hash value different than the one provided. You should
/// always validate the returned values before using them.
pub struct RawIterHash<'a, T, A: Allocator + Clone = Global> {
inner: RawIterHashInner<'a, A>,
_marker: PhantomData<T>,
@@ -2174,6 +2302,7 @@ struct RawIterHashInner<'a, A: Allocator + Clone> {
impl<'a, T, A: Allocator + Clone> RawIterHash<'a, T, A> {
#[cfg_attr(feature = "inline-more", inline)]
#[cfg(feature = "raw")]
fn new(table: &'a RawTable<T, A>, hash: u64) -> Self {
RawIterHash {
inner: RawIterHashInner::new(&table.table, hash),
@@ -2183,6 +2312,7 @@ impl<'a, T, A: Allocator + Clone> RawIterHash<'a, T, A> {
}
impl<'a, A: Allocator + Clone> RawIterHashInner<'a, A> {
#[cfg_attr(feature = "inline-more", inline)]
#[cfg(feature = "raw")]
fn new(table: &'a RawTableInner<A>, hash: u64) -> Self {
unsafe {
let h2_hash = h2(hash);
@@ -2239,6 +2369,20 @@ impl<'a, A: Allocator + Clone> Iterator for RawIterHashInner<'a, A> {
mod test_map {
use super::*;
fn rehash_in_place<T>(table: &mut RawTable<T>, hasher: impl Fn(&T) -> u64) {
unsafe {
table.table.rehash_in_place(
&|table, index| hasher(table.bucket::<T>(index).as_ref()),
mem::size_of::<T>(),
if mem::needs_drop::<T>() {
Some(mem::transmute(ptr::drop_in_place::<T> as unsafe fn(*mut T)))
} else {
None
},
);
}
}
#[test]
fn rehash() {
let mut table = RawTable::new();
@@ -2254,7 +2398,7 @@ mod test_map {
assert!(table.find(i + 100, |x| *x == i + 100).is_none());
}
table.rehash_in_place(hasher);
rehash_in_place(&mut table, hasher);
for i in 0..100 {
unsafe {
+1
View File
@@ -28,6 +28,7 @@ impl Group {
/// value for an empty hash table.
///
/// This is guaranteed to be aligned to the group size.
#[inline]
#[allow(clippy::items_after_statements)]
pub const fn static_empty() -> &'static [u8; Group::WIDTH] {
#[repr(C)]
+1 -1
View File
@@ -44,6 +44,6 @@ where
{
#[inline]
fn drop(&mut self) {
(self.dropfn)(&mut self.value)
(self.dropfn)(&mut self.value);
}
}
+12 -12
View File
@@ -378,7 +378,7 @@ impl<T, S, A: Allocator + Clone> HashSet<T, S, A> {
/// ```
#[cfg_attr(feature = "inline-more", inline)]
pub fn clear(&mut self) {
self.map.clear()
self.map.clear();
}
}
@@ -559,7 +559,7 @@ where
/// ```
#[cfg_attr(feature = "inline-more", inline)]
pub fn reserve(&mut self, additional: usize) {
self.map.reserve(additional)
self.map.reserve(additional);
}
/// Tries to reserve capacity for at least `additional` more elements to be inserted
@@ -601,7 +601,7 @@ where
/// ```
#[cfg_attr(feature = "inline-more", inline)]
pub fn shrink_to_fit(&mut self) {
self.map.shrink_to_fit()
self.map.shrink_to_fit();
}
/// Shrinks the capacity of the set with a lower limit. It will drop
@@ -627,7 +627,7 @@ where
/// ```
#[cfg_attr(feature = "inline-more", inline)]
pub fn shrink_to(&mut self, min_capacity: usize) {
self.map.shrink_to(min_capacity)
self.map.shrink_to(min_capacity);
}
/// Visits the values representing the difference,
@@ -1168,7 +1168,7 @@ where
{
#[cfg_attr(feature = "inline-more", inline)]
fn extend<I: IntoIterator<Item = &'a T>>(&mut self, iter: I) {
self.extend(iter.into_iter().cloned());
self.extend(iter.into_iter().copied());
}
#[inline]
@@ -1969,7 +1969,7 @@ mod test_set {
let expected = [3, 5, 11, 77];
for x in a.intersection(&b) {
assert!(expected.contains(x));
i += 1
i += 1;
}
assert_eq!(i, expected.len());
}
@@ -1992,7 +1992,7 @@ mod test_set {
let expected = [1, 5, 11];
for x in a.difference(&b) {
assert!(expected.contains(x));
i += 1
i += 1;
}
assert_eq!(i, expected.len());
}
@@ -2018,7 +2018,7 @@ mod test_set {
let expected = [-2, 1, 5, 11, 14, 22];
for x in a.symmetric_difference(&b) {
assert!(expected.contains(x));
i += 1
i += 1;
}
assert_eq!(i, expected.len());
}
@@ -2048,7 +2048,7 @@ mod test_set {
let expected = [-2, 1, 3, 5, 9, 11, 13, 16, 19, 24];
for x in a.union(&b) {
assert!(expected.contains(x));
i += 1
i += 1;
}
assert_eq!(i, expected.len());
}
@@ -2074,7 +2074,7 @@ mod test_set {
fn test_from_iter() {
let xs = [1, 2, 2, 3, 4, 5, 6, 7, 8, 9];
let set: HashSet<_> = xs.iter().cloned().collect();
let set: HashSet<_> = xs.iter().copied().collect();
for x in &xs {
assert!(set.contains(x));
@@ -2236,7 +2236,7 @@ mod test_set {
#[test]
fn test_retain() {
let xs = [1, 2, 3, 4, 5, 6];
let mut set: HashSet<i32> = xs.iter().cloned().collect();
let mut set: HashSet<i32> = xs.iter().copied().collect();
set.retain(|&k| k % 2 == 0);
assert_eq!(set.len(), 3);
assert!(set.contains(&2));
@@ -2278,7 +2278,7 @@ mod test_set {
const EMPTY_SET: HashSet<u32, MyHasher> = HashSet::with_hasher(MyHasher);
let mut set = EMPTY_SET.clone();
let mut set = EMPTY_SET;
set.insert(19);
assert!(set.contains(&19));
}
+35 -35
View File
@@ -269,20 +269,20 @@ fn map_seq_par_equivalence_existing_empty_extend_empty() {
let mut map_seq = MAP_EXISTING_EMPTY.clone();
let mut map_par = MAP_EXISTING_EMPTY.clone();
map_seq.extend(MAP_EXTENSION_EMPTY.iter().cloned());
map_par.par_extend(MAP_EXTENSION_EMPTY.par_iter().cloned());
map_seq.extend(MAP_EXTENSION_EMPTY.iter().copied());
map_par.par_extend(MAP_EXTENSION_EMPTY.par_iter().copied());
assert_eq3!(map_seq, map_par, expected);
}
#[test]
fn map_seq_par_equivalence_existing_empty_extend() {
let expected = MAP_EXTENSION.iter().cloned().collect::<HashMap<_, _>>();
let expected = MAP_EXTENSION.iter().copied().collect::<HashMap<_, _>>();
let mut map_seq = MAP_EXISTING_EMPTY.clone();
let mut map_par = MAP_EXISTING_EMPTY.clone();
map_seq.extend(MAP_EXTENSION.iter().cloned());
map_par.par_extend(MAP_EXTENSION.par_iter().cloned());
map_seq.extend(MAP_EXTENSION.iter().copied());
map_par.par_extend(MAP_EXTENSION.par_iter().copied());
assert_eq3!(map_seq, map_par, expected);
}
@@ -293,8 +293,8 @@ fn map_seq_par_equivalence_existing_extend_empty() {
let mut map_seq = MAP_EXISTING.clone();
let mut map_par = MAP_EXISTING.clone();
map_seq.extend(MAP_EXTENSION_EMPTY.iter().cloned());
map_par.par_extend(MAP_EXTENSION_EMPTY.par_iter().cloned());
map_seq.extend(MAP_EXTENSION_EMPTY.iter().copied());
map_par.par_extend(MAP_EXTENSION_EMPTY.par_iter().copied());
assert_eq3!(map_seq, map_par, expected);
}
@@ -305,8 +305,8 @@ fn map_seq_par_equivalence_existing_extend() {
let mut map_seq = MAP_EXISTING.clone();
let mut map_par = MAP_EXISTING.clone();
map_seq.extend(MAP_EXTENSION.iter().cloned());
map_par.par_extend(MAP_EXTENSION.par_iter().cloned());
map_seq.extend(MAP_EXTENSION.iter().copied());
map_par.par_extend(MAP_EXTENSION.par_iter().copied());
assert_eq3!(map_seq, map_par, expected);
}
@@ -423,20 +423,20 @@ fn set_seq_par_equivalence_existing_empty_extend_empty() {
let mut set_seq = SET_EXISTING_EMPTY.clone();
let mut set_par = SET_EXISTING_EMPTY.clone();
set_seq.extend(SET_EXTENSION_EMPTY.iter().cloned());
set_par.par_extend(SET_EXTENSION_EMPTY.par_iter().cloned());
set_seq.extend(SET_EXTENSION_EMPTY.iter().copied());
set_par.par_extend(SET_EXTENSION_EMPTY.par_iter().copied());
assert_eq3!(set_seq, set_par, expected);
}
#[test]
fn set_seq_par_equivalence_existing_empty_extend() {
let expected = SET_EXTENSION.iter().cloned().collect::<HashSet<_>>();
let expected = SET_EXTENSION.iter().copied().collect::<HashSet<_>>();
let mut set_seq = SET_EXISTING_EMPTY.clone();
let mut set_par = SET_EXISTING_EMPTY.clone();
set_seq.extend(SET_EXTENSION.iter().cloned());
set_par.par_extend(SET_EXTENSION.par_iter().cloned());
set_seq.extend(SET_EXTENSION.iter().copied());
set_par.par_extend(SET_EXTENSION.par_iter().copied());
assert_eq3!(set_seq, set_par, expected);
}
@@ -447,8 +447,8 @@ fn set_seq_par_equivalence_existing_extend_empty() {
let mut set_seq = SET_EXISTING.clone();
let mut set_par = SET_EXISTING.clone();
set_seq.extend(SET_EXTENSION_EMPTY.iter().cloned());
set_par.par_extend(SET_EXTENSION_EMPTY.par_iter().cloned());
set_seq.extend(SET_EXTENSION_EMPTY.iter().copied());
set_par.par_extend(SET_EXTENSION_EMPTY.par_iter().copied());
assert_eq3!(set_seq, set_par, expected);
}
@@ -459,37 +459,37 @@ fn set_seq_par_equivalence_existing_extend() {
let mut set_seq = SET_EXISTING.clone();
let mut set_par = SET_EXISTING.clone();
set_seq.extend(SET_EXTENSION.iter().cloned());
set_par.par_extend(SET_EXTENSION.par_iter().cloned());
set_seq.extend(SET_EXTENSION.iter().copied());
set_par.par_extend(SET_EXTENSION.par_iter().copied());
assert_eq3!(set_seq, set_par, expected);
}
lazy_static! {
static ref SET_A: HashSet<char> = ['a', 'b', 'c', 'd'].iter().cloned().collect();
static ref SET_B: HashSet<char> = ['a', 'b', 'e', 'f'].iter().cloned().collect();
static ref SET_DIFF_AB: HashSet<char> = ['c', 'd'].iter().cloned().collect();
static ref SET_DIFF_BA: HashSet<char> = ['e', 'f'].iter().cloned().collect();
static ref SET_SYMM_DIFF_AB: HashSet<char> = ['c', 'd', 'e', 'f'].iter().cloned().collect();
static ref SET_INTERSECTION_AB: HashSet<char> = ['a', 'b'].iter().cloned().collect();
static ref SET_A: HashSet<char> = ['a', 'b', 'c', 'd'].iter().copied().collect();
static ref SET_B: HashSet<char> = ['a', 'b', 'e', 'f'].iter().copied().collect();
static ref SET_DIFF_AB: HashSet<char> = ['c', 'd'].iter().copied().collect();
static ref SET_DIFF_BA: HashSet<char> = ['e', 'f'].iter().copied().collect();
static ref SET_SYMM_DIFF_AB: HashSet<char> = ['c', 'd', 'e', 'f'].iter().copied().collect();
static ref SET_INTERSECTION_AB: HashSet<char> = ['a', 'b'].iter().copied().collect();
static ref SET_UNION_AB: HashSet<char> =
['a', 'b', 'c', 'd', 'e', 'f'].iter().cloned().collect();
['a', 'b', 'c', 'd', 'e', 'f'].iter().copied().collect();
}
#[test]
fn set_seq_par_equivalence_difference() {
let diff_ab_seq = SET_A.difference(&*SET_B).cloned().collect::<HashSet<_>>();
let diff_ab_seq = SET_A.difference(&*SET_B).copied().collect::<HashSet<_>>();
let diff_ab_par = SET_A
.par_difference(&*SET_B)
.cloned()
.copied()
.collect::<HashSet<_>>();
assert_eq3!(diff_ab_seq, diff_ab_par, *SET_DIFF_AB);
let diff_ba_seq = SET_B.difference(&*SET_A).cloned().collect::<HashSet<_>>();
let diff_ba_seq = SET_B.difference(&*SET_A).copied().collect::<HashSet<_>>();
let diff_ba_par = SET_B
.par_difference(&*SET_A)
.cloned()
.copied()
.collect::<HashSet<_>>();
assert_eq3!(diff_ba_seq, diff_ba_par, *SET_DIFF_BA);
@@ -499,11 +499,11 @@ fn set_seq_par_equivalence_difference() {
fn set_seq_par_equivalence_symmetric_difference() {
let symm_diff_ab_seq = SET_A
.symmetric_difference(&*SET_B)
.cloned()
.copied()
.collect::<HashSet<_>>();
let symm_diff_ab_par = SET_A
.par_symmetric_difference(&*SET_B)
.cloned()
.copied()
.collect::<HashSet<_>>();
assert_eq3!(symm_diff_ab_seq, symm_diff_ab_par, *SET_SYMM_DIFF_AB);
@@ -511,10 +511,10 @@ fn set_seq_par_equivalence_symmetric_difference() {
#[test]
fn set_seq_par_equivalence_intersection() {
let intersection_ab_seq = SET_A.intersection(&*SET_B).cloned().collect::<HashSet<_>>();
let intersection_ab_seq = SET_A.intersection(&*SET_B).copied().collect::<HashSet<_>>();
let intersection_ab_par = SET_A
.par_intersection(&*SET_B)
.cloned()
.copied()
.collect::<HashSet<_>>();
assert_eq3!(
@@ -526,8 +526,8 @@ fn set_seq_par_equivalence_intersection() {
#[test]
fn set_seq_par_equivalence_union() {
let union_ab_seq = SET_A.union(&*SET_B).cloned().collect::<HashSet<_>>();
let union_ab_par = SET_A.par_union(&*SET_B).cloned().collect::<HashSet<_>>();
let union_ab_seq = SET_A.union(&*SET_B).copied().collect::<HashSet<_>>();
let union_ab_par = SET_A.par_union(&*SET_B).copied().collect::<HashSet<_>>();
assert_eq3!(union_ab_seq, union_ab_par, *SET_UNION_AB);
}
+17 -16
View File
@@ -2,35 +2,36 @@
use hashbrown::HashSet;
use rand::{distributions::Alphanumeric, rngs::SmallRng, Rng, SeedableRng};
use std::iter;
#[test]
fn test_hashset_insert_remove() {
let mut m: HashSet<Vec<char>> = HashSet::new();
//let num: u32 = 4096;
//let tx: Vec<Vec<u8>> = (0..num).map(|i| (i..(16 + i)).collect()).collect();
let seed: [u8; 32] = [
130, 220, 246, 217, 111, 124, 221, 189, 190, 234, 121, 93, 67, 95, 100, 43, // again
130, 220, 246, 217, 111, 124, 221, 189, 190, 234, 121, 93, 67, 95, 100, 43,
];
let rng = &mut SmallRng::from_seed(seed);
let tx: Vec<Vec<char>> = (0..4096)
.map(|_| {
rng.sample_iter(&Alphanumeric)
.take(32)
.map(char::from)
.collect()
})
.collect();
let tx: Vec<Vec<char>> = iter::repeat_with(|| {
rng.sample_iter(&Alphanumeric)
.take(32)
.map(char::from)
.collect()
})
.take(4096)
.collect();
// more readable with explicit `true` / `false`
#[allow(clippy::bool_assert_comparison)]
for _ in 0..32 {
for i in 0..4096 {
assert_eq!(m.contains(&tx[i].clone()), false);
assert_eq!(m.insert(tx[i].clone()), true);
for x in &tx {
assert_eq!(m.contains(x), false);
assert_eq!(m.insert(x.clone()), true);
}
for i in 0..4096 {
println!("removing {} {:?}", i, tx[i]);
assert_eq!(m.remove(&tx[i]), true);
for (i, x) in tx.iter().enumerate() {
println!("removing {} {:?}", i, x);
assert_eq!(m.remove(x), true);
}
}
}