Replace the raw module with just hash_map
When I implemented Extend<Box<A>> for Map<A>, I thought:
can I implement Extend<(TypeId, Box<A>)> for RawMap<A>,
as HashMap<K, V> implements Extend<(K, V)>?
No, I responded, for insert is unsafe,
and a trait implementation cannot be marked unsafe.
Then said I, hang on, why is insert unsafe?
For by analogy with pointers, creating a dangling pointer is safe,
it’s only dereferencing it that’s unsafe;
so too here could insertion be safe and retrieval unsafe.
Then I realised: RawMap is actually completely safe, of itself;
the reason the unsafety is needed is AsMut<RawMap<A>> for Map<A>:
that retrieval is defined as safe, so insertion need be done delicately.
And so I consulted with myself and wondered:
Would it not be better to drop AsMut,
exposing rather an `unsafe fn as_raw_mut`?
For `AsRef<RawMap<A>>` and `Into<RawMap<A>>` may yet be safe,
yet this would take RawMap towards parity with HashMap.
And yet further went I,
descending into depths unplumbed these five years,
saying unto myself:
Wherefore RawMap<A> at all?
Why not rather HashMap<TypeId, Box<A>, BuildHasherDefault<TypeIdHasher>>,
accessed freely and safely by reference or Into,
and unsafely as discussed in the previous stanza
(if I may call it such)?
Striving to understand the matter,
it was a wearisome effort,
and I could not.
I consulted with 143ee06268
,
yet with the passage of nigh seven years it was not able to tell me
just why I had thought this might be a good idea.
For lo, those were the benighted ages before Rust 1.0.0,
though the glimmer of that bright dawn danced on the horizon
like the latter part of an arctic winter.
And so, casting away the trammels of history
I forged a new path.
For lo! had I not even then declared it
“not necessarily the final form”?
Casting RawMap away from me and clasping HashMap to my bosom,
I found the required diff in lib.rs
such a delicate thing,
so slight.
It was pleasing in my eyes, and so forthwith I decided:
hew down the unwanted abstraction,
and bind it with a band of iron and bronze,
that it may grow no more.
So need I not add more features to it,
mere shadows of the true HashMap underneath.
Oh fortunate day!
Three-hundred-odd lines removed,
(though more detailed comments offset this,
so that the end result is more like 223,)
and simplicity restored.
Well, except for this fly in the ointment:
std versus hashbrown.
Woe unto the person who calls a raw map std::collections::HashMap,
for when another comes and enables hashbrown,
the first shall crumble into nothingness and errors most distressing.
The mitigation of this is `pub type RawMap<A>`,
augmented by the very truth that,
few using this feature,
few may stumble!
Yet there are difference betwixt the twain,
seen in my cfg branching on VacantEntry and OccupiedEntry,
and this *is* a very mild violation of the principle of strictly additive features.
There ’tis: the tale of an abstraction unravelled.
Unravelled? Feels more like “not ravelled” rather than undoing ravelling.
Deravelled? Disravelled?
I shall but brand the abstraction a dead princess, as it were,
and this my pavane pour un infante défunte.
And if you, dear reader—
if reader there be of this screed that has grown rather longer than originally anticipated but also probably more entertaining if you don’t mind this sort of thing or share a similar sense of humour to me—
have aught to opine on the matter,
You know my email address.
This commit is contained in:
parent
2bcbd9c551
commit
27eca55182
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -24,8 +24,14 @@
|
||||||
|
|
||||||
- Implemented `Default` on `Map` (not just on `RawMap`).
|
- Implemented `Default` on `Map` (not just on `RawMap`).
|
||||||
|
|
||||||
- The implementation of `Into<RawMap<A>>` for `Map<A>` has been
|
- Removed the `anymap::raw` wrapper layer around `std::collections::hash_map`,
|
||||||
replaced with the more general `From<Map<A>>` for `RawMap<A>`.
|
in favour of exposing the raw `HashMap` directly. I think there was a reason
|
||||||
|
I did it that seven years ago, but I think that reason may have dissolved by
|
||||||
|
now, and I can’t think of it and I don’t like the particular safe
|
||||||
|
`as_mut`/unsafe insert approach that I used. Because of the hashbrown stuff,
|
||||||
|
I have retained `anymap::RawMap` is an alias, and `anymap::raw_hash_map` too.
|
||||||
|
The end result of this is that raw access can finally access things that have
|
||||||
|
stabilised since Rust 1.7.0, and we’ll no longer need to play catch-up.
|
||||||
|
|
||||||
- Worked around the spurious `where_clauses_object_safety` future-compatibility lint that has been raised since mid-2018.
|
- Worked around the spurious `where_clauses_object_safety` future-compatibility lint that has been raised since mid-2018.
|
||||||
If you put `#![allow(where_clauses_object_safety)]` on your binary crates for this reason, you can remove it.
|
If you put `#![allow(where_clauses_object_safety)]` on your binary crates for this reason, you can remove it.
|
||||||
|
|
208
src/lib.rs
208
src/lib.rs
|
@ -7,6 +7,8 @@
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
use core::any::{Any, TypeId};
|
use core::any::{Any, TypeId};
|
||||||
|
use core::convert::TryInto;
|
||||||
|
use core::hash::{Hasher, BuildHasherDefault};
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
#[cfg(not(any(feature = "std", feature = "hashbrown")))]
|
#[cfg(not(any(feature = "std", feature = "hashbrown")))]
|
||||||
|
@ -18,10 +20,34 @@ extern crate alloc;
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
|
|
||||||
use raw::RawMap;
|
|
||||||
use any::{UncheckedAnyExt, IntoBox};
|
use any::{UncheckedAnyExt, IntoBox};
|
||||||
pub use any::CloneAny;
|
pub use any::CloneAny;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "std", not(feature = "hashbrown")))]
|
||||||
|
/// A re-export of [`std::collections::hash_map`] for raw access.
|
||||||
|
///
|
||||||
|
/// If the `hashbrown` feature gets enabled, this will become an export of `hashbrown::hash_map`.
|
||||||
|
///
|
||||||
|
/// As with [`RawMap`][crate::RawMap], this is exposed for compatibility reasons, since features
|
||||||
|
/// are supposed to be additive. This *is* imperfect, since the two modules are incompatible in a
|
||||||
|
/// few places (e.g. hashbrown’s entry types have an extra generic parameter), but it’s close, and
|
||||||
|
/// much too useful to give up the whole concept.
|
||||||
|
pub use std::collections::hash_map as raw_hash_map;
|
||||||
|
|
||||||
|
#[cfg(feature = "hashbrown")]
|
||||||
|
/// A re-export of [`hashbrown::hash_map`] for raw access.
|
||||||
|
///
|
||||||
|
/// If the `hashbrown` feature was disabled, this would become an export of
|
||||||
|
/// `std::collections::hash_map`.
|
||||||
|
///
|
||||||
|
/// As with [`RawMap`][crate::RawMap], this is exposed for compatibility reasons, since features
|
||||||
|
/// are supposed to be additive. This *is* imperfect, since the two modules are incompatible in a
|
||||||
|
/// few places (e.g. hashbrown’s entry types have an extra generic parameter), but it’s close, and
|
||||||
|
/// much too useful to give up the whole concept.
|
||||||
|
pub use hashbrown::hash_map as raw_hash_map;
|
||||||
|
|
||||||
|
use self::raw_hash_map::HashMap;
|
||||||
|
|
||||||
macro_rules! impl_common_methods {
|
macro_rules! impl_common_methods {
|
||||||
(
|
(
|
||||||
field: $t:ident.$field:ident;
|
field: $t:ident.$field:ident;
|
||||||
|
@ -104,7 +130,17 @@ macro_rules! impl_common_methods {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod any;
|
mod any;
|
||||||
pub mod raw;
|
|
||||||
|
/// Raw access to the underlying `HashMap`.
|
||||||
|
///
|
||||||
|
/// This is a public type alias because the underlying `HashMap` could be
|
||||||
|
/// `std::collections::HashMap` or `hashbrown::HashMap`, depending on the crate features enabled.
|
||||||
|
/// For that reason, you should refer to this type as `anymap::RawMap` rather than
|
||||||
|
/// `std::collections::HashMap` to avoid breakage if something else in your crate tree enables
|
||||||
|
/// hashbrown.
|
||||||
|
///
|
||||||
|
/// See also [`raw_hash_map`], an export of the corresponding `hash_map` module.
|
||||||
|
pub type RawMap<A> = HashMap<TypeId, Box<A>, BuildHasherDefault<TypeIdHasher>>;
|
||||||
|
|
||||||
/// A collection containing zero or one values for any given type and allowing convenient,
|
/// A collection containing zero or one values for any given type and allowing convenient,
|
||||||
/// type-safe access to those values.
|
/// type-safe access to those values.
|
||||||
|
@ -176,8 +212,8 @@ pub type AnyMap = Map<dyn Any>;
|
||||||
|
|
||||||
impl_common_methods! {
|
impl_common_methods! {
|
||||||
field: Map.raw;
|
field: Map.raw;
|
||||||
new() => RawMap::new();
|
new() => RawMap::with_hasher(Default::default());
|
||||||
with_capacity(capacity) => RawMap::with_capacity(capacity);
|
with_capacity(capacity) => RawMap::with_capacity_and_hasher(capacity, Default::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: ?Sized + UncheckedAnyExt> Map<A> {
|
impl<A: ?Sized + UncheckedAnyExt> Map<A> {
|
||||||
|
@ -227,57 +263,121 @@ impl<A: ?Sized + UncheckedAnyExt> Map<A> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn entry<T: IntoBox<A>>(&mut self) -> Entry<A, T> {
|
pub fn entry<T: IntoBox<A>>(&mut self) -> Entry<A, T> {
|
||||||
match self.raw.entry(TypeId::of::<T>()) {
|
match self.raw.entry(TypeId::of::<T>()) {
|
||||||
raw::Entry::Occupied(e) => Entry::Occupied(OccupiedEntry {
|
raw_hash_map::Entry::Occupied(e) => Entry::Occupied(OccupiedEntry {
|
||||||
inner: e,
|
inner: e,
|
||||||
type_: PhantomData,
|
type_: PhantomData,
|
||||||
}),
|
}),
|
||||||
raw::Entry::Vacant(e) => Entry::Vacant(VacantEntry {
|
raw_hash_map::Entry::Vacant(e) => Entry::Vacant(VacantEntry {
|
||||||
inner: e,
|
inner: e,
|
||||||
type_: PhantomData,
|
type_: PhantomData,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get access to the raw hash map that backs this.
|
||||||
|
///
|
||||||
|
/// This will seldom be useful, but it’s conceivable that you could wish to iterate over all
|
||||||
|
/// the items in the collection, and this lets you do that.
|
||||||
|
///
|
||||||
|
/// To improve compatibility with Cargo features, interact with this map through the names
|
||||||
|
/// [`anymap::RawMap`][RawMap] and [`anymap::raw_hash_map`][raw_hash_map], rather than through
|
||||||
|
/// `std::collections::{HashMap, hash_map}` or `hashbrown::{HashMap, hash_map}`, for anything
|
||||||
|
/// beyond self methods. Otherwise, if you use std and another crate in the tree enables
|
||||||
|
/// hashbrown, your code will break.
|
||||||
|
#[inline]
|
||||||
|
pub fn as_raw(&self) -> &RawMap<A> {
|
||||||
|
&self.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get mutable access to the raw hash map that backs this.
|
||||||
|
///
|
||||||
|
/// This will seldom be useful, but it’s conceivable that you could wish to iterate over all
|
||||||
|
/// the items in the collection mutably, or drain or something, or *possibly* even batch
|
||||||
|
/// insert, and this lets you do that.
|
||||||
|
///
|
||||||
|
/// To improve compatibility with Cargo features, interact with this map through the names
|
||||||
|
/// [`anymap::RawMap`][RawMap] and [`anymap::raw_hash_map`][raw_hash_map], rather than through
|
||||||
|
/// `std::collections::{HashMap, hash_map}` or `hashbrown::{HashMap, hash_map}`, for anything
|
||||||
|
/// beyond self methods. Otherwise, if you use std and another crate in the tree enables
|
||||||
|
/// hashbrown, your code will break.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// If you insert any values to the raw map, the key (a `TypeId`) must match the value’s type,
|
||||||
|
/// or *undefined behaviour* will occur when you access those values.
|
||||||
|
///
|
||||||
|
/// (*Removing* entries is perfectly safe.)
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn as_raw_mut(&mut self) -> &mut RawMap<A> {
|
||||||
|
&mut self.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert this into the raw hash map that backs this.
|
||||||
|
///
|
||||||
|
/// This will seldom be useful, but it’s conceivable that you could wish to consume all the
|
||||||
|
/// items in the collection and do *something* with some or all of them, and this lets you do
|
||||||
|
/// that, without the `unsafe` that `.as_raw_mut().drain()` would require.
|
||||||
|
///
|
||||||
|
/// To improve compatibility with Cargo features, interact with this map through the names
|
||||||
|
/// [`anymap::RawMap`][RawMap] and [`anymap::raw_hash_map`][raw_hash_map], rather than through
|
||||||
|
/// `std::collections::{HashMap, hash_map}` or `hashbrown::{HashMap, hash_map}`, for anything
|
||||||
|
/// beyond self methods. Otherwise, if you use std and another crate in the tree enables
|
||||||
|
/// hashbrown, your code will break.
|
||||||
|
#[inline]
|
||||||
|
pub fn into_raw(self) -> RawMap<A> {
|
||||||
|
self.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a map from a collection of raw values.
|
||||||
|
///
|
||||||
|
/// You know what? I can’t immediately think of any legitimate use for this, especially because
|
||||||
|
/// of the requirement of the `BuildHasherDefault<TypeIdHasher>` generic in the map.
|
||||||
|
///
|
||||||
|
/// Perhaps this will be most practical as `unsafe { Map::from_raw(iter.collect()) }`, iter
|
||||||
|
/// being an iterator over `(TypeId, Box<A>)` pairs. Eh, this method provides symmetry with
|
||||||
|
/// `into_raw`, so I don’t care if literally no one ever uses it. I’m not even going to write a
|
||||||
|
/// test for it, it’s so trivial.
|
||||||
|
///
|
||||||
|
/// To improve compatibility with Cargo features, interact with this map through the names
|
||||||
|
/// [`anymap::RawMap`][RawMap] and [`anymap::raw_hash_map`][raw_hash_map], rather than through
|
||||||
|
/// `std::collections::{HashMap, hash_map}` or `hashbrown::{HashMap, hash_map}`, for anything
|
||||||
|
/// beyond self methods. Otherwise, if you use std and another crate in the tree enables
|
||||||
|
/// hashbrown, your code will break.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// For all entries in the raw map, the key (a `TypeId`) must match the value’s type,
|
||||||
|
/// or *undefined behaviour* will occur when you access that entry.
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn from_raw(raw: RawMap<A>) -> Map<A> {
|
||||||
|
Self { raw }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: ?Sized + UncheckedAnyExt> Extend<Box<A>> for Map<A> {
|
impl<A: ?Sized + UncheckedAnyExt> Extend<Box<A>> for Map<A> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn extend<T: IntoIterator<Item = Box<A>>>(&mut self, iter: T) {
|
fn extend<T: IntoIterator<Item = Box<A>>>(&mut self, iter: T) {
|
||||||
for item in iter {
|
for item in iter {
|
||||||
let _ = unsafe { self.raw.insert(item.type_id(), item) };
|
let _ = self.raw.insert(item.type_id(), item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: ?Sized + UncheckedAnyExt> AsRef<RawMap<A>> for Map<A> {
|
|
||||||
#[inline]
|
|
||||||
fn as_ref(&self) -> &RawMap<A> {
|
|
||||||
&self.raw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: ?Sized + UncheckedAnyExt> AsMut<RawMap<A>> for Map<A> {
|
|
||||||
#[inline]
|
|
||||||
fn as_mut(&mut self) -> &mut RawMap<A> {
|
|
||||||
&mut self.raw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: ?Sized + UncheckedAnyExt> From<Map<A>> for RawMap<A> {
|
|
||||||
#[inline]
|
|
||||||
fn from(map: Map<A>) -> RawMap<A> {
|
|
||||||
map.raw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A view into a single occupied location in an `Map`.
|
/// A view into a single occupied location in an `Map`.
|
||||||
pub struct OccupiedEntry<'a, A: ?Sized + UncheckedAnyExt, V: 'a> {
|
pub struct OccupiedEntry<'a, A: ?Sized + UncheckedAnyExt, V: 'a> {
|
||||||
inner: raw::OccupiedEntry<'a, A>,
|
#[cfg(all(feature = "std", not(feature = "hashbrown")))]
|
||||||
|
inner: raw_hash_map::OccupiedEntry<'a, TypeId, Box<A>>,
|
||||||
|
#[cfg(feature = "hashbrown")]
|
||||||
|
inner: raw_hash_map::OccupiedEntry<'a, TypeId, Box<A>, BuildHasherDefault<TypeIdHasher>>,
|
||||||
type_: PhantomData<V>,
|
type_: PhantomData<V>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A view into a single empty location in an `Map`.
|
/// A view into a single empty location in an `Map`.
|
||||||
pub struct VacantEntry<'a, A: ?Sized + UncheckedAnyExt, V: 'a> {
|
pub struct VacantEntry<'a, A: ?Sized + UncheckedAnyExt, V: 'a> {
|
||||||
inner: raw::VacantEntry<'a, A>,
|
#[cfg(all(feature = "std", not(feature = "hashbrown")))]
|
||||||
|
inner: raw_hash_map::VacantEntry<'a, TypeId, Box<A>>,
|
||||||
|
#[cfg(feature = "hashbrown")]
|
||||||
|
inner: raw_hash_map::VacantEntry<'a, TypeId, Box<A>, BuildHasherDefault<TypeIdHasher>>,
|
||||||
type_: PhantomData<V>,
|
type_: PhantomData<V>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -353,6 +453,35 @@ impl<'a, A: ?Sized + UncheckedAnyExt, V: IntoBox<A>> VacantEntry<'a, A, V> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A hasher designed to eke a little more speed out, given `TypeId`’s known characteristics.
|
||||||
|
///
|
||||||
|
/// Specifically, this is a no-op hasher that expects to be fed a u64’s worth of
|
||||||
|
/// randomly-distributed bits. It works well for `TypeId` (eliminating start-up time, so that my
|
||||||
|
/// get_missing benchmark is ~30ns rather than ~900ns, and being a good deal faster after that, so
|
||||||
|
/// that my insert_and_get_on_260_types benchmark is ~12μs instead of ~21.5μs), but will
|
||||||
|
/// panic in debug mode and always emit zeros in release mode for any other sorts of inputs, so
|
||||||
|
/// yeah, don’t use it! 😀
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct TypeIdHasher {
|
||||||
|
value: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hasher for TypeIdHasher {
|
||||||
|
#[inline]
|
||||||
|
fn write(&mut self, bytes: &[u8]) {
|
||||||
|
// This expects to receive exactly one 64-bit value, and there’s no realistic chance of
|
||||||
|
// that changing, but I don’t want to depend on something that isn’t expressly part of the
|
||||||
|
// contract for safety. But I’m OK with release builds putting everything in one bucket
|
||||||
|
// if it *did* change (and debug builds panicking).
|
||||||
|
debug_assert_eq!(bytes.len(), 8);
|
||||||
|
let _ = bytes.try_into()
|
||||||
|
.map(|array| self.value = u64::from_ne_bytes(array));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn finish(&self) -> u64 { self.value }
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -487,4 +616,23 @@ mod tests {
|
||||||
assert_debug::<Map<dyn CloneAny + Send>>();
|
assert_debug::<Map<dyn CloneAny + Send>>();
|
||||||
assert_debug::<Map<dyn CloneAny + Send + Sync>>();
|
assert_debug::<Map<dyn CloneAny + Send + Sync>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_id_hasher() {
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use core::hash::Hash;
|
||||||
|
fn verify_hashing_with(type_id: TypeId) {
|
||||||
|
let mut hasher = TypeIdHasher::default();
|
||||||
|
type_id.hash(&mut hasher);
|
||||||
|
// SAFETY: u64 is valid for all bit patterns.
|
||||||
|
assert_eq!(hasher.finish(), unsafe { core::mem::transmute::<TypeId, u64>(type_id) });
|
||||||
|
}
|
||||||
|
// Pick a variety of types, just to demonstrate it’s all sane. Normal, zero-sized, unsized, &c.
|
||||||
|
verify_hashing_with(TypeId::of::<usize>());
|
||||||
|
verify_hashing_with(TypeId::of::<()>());
|
||||||
|
verify_hashing_with(TypeId::of::<str>());
|
||||||
|
verify_hashing_with(TypeId::of::<&str>());
|
||||||
|
verify_hashing_with(TypeId::of::<Vec<u8>>());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
371
src/raw.rs
371
src/raw.rs
|
@ -1,371 +0,0 @@
|
||||||
//! The raw form of a `Map`, allowing untyped access.
|
|
||||||
//!
|
|
||||||
//! All relevant details are in the `RawMap` struct.
|
|
||||||
|
|
||||||
use core::any::{Any, TypeId};
|
|
||||||
use core::borrow::Borrow;
|
|
||||||
#[cfg(all(feature = "std", not(feature = "hashbrown")))]
|
|
||||||
use std::collections::hash_map::{self, HashMap};
|
|
||||||
#[cfg(feature = "hashbrown")]
|
|
||||||
use hashbrown::hash_map::{self, HashMap};
|
|
||||||
#[cfg(not(feature = "std"))]
|
|
||||||
use alloc::boxed::Box;
|
|
||||||
use core::convert::TryInto;
|
|
||||||
use core::hash::Hash;
|
|
||||||
use core::hash::{Hasher, BuildHasherDefault};
|
|
||||||
use core::ops::{Index, IndexMut};
|
|
||||||
|
|
||||||
use crate::any::UncheckedAnyExt;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct TypeIdHasher {
|
|
||||||
value: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hasher for TypeIdHasher {
|
|
||||||
#[inline]
|
|
||||||
fn write(&mut self, bytes: &[u8]) {
|
|
||||||
// This expects to receive exactly one 64-bit value, and there’s no realistic chance of
|
|
||||||
// that changing, but I don’t want to depend on something that isn’t expressly part of the
|
|
||||||
// contract for safety. But I’m OK with release builds putting everything in one bucket
|
|
||||||
// if it *did* change (and debug builds panicking).
|
|
||||||
debug_assert_eq!(bytes.len(), 8);
|
|
||||||
let _ = bytes.try_into()
|
|
||||||
.map(|array| self.value = u64::from_ne_bytes(array));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn finish(&self) -> u64 { self.value }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn type_id_hasher() {
|
|
||||||
#[cfg(not(feature = "std"))]
|
|
||||||
use alloc::vec::Vec;
|
|
||||||
fn verify_hashing_with(type_id: TypeId) {
|
|
||||||
let mut hasher = TypeIdHasher::default();
|
|
||||||
type_id.hash(&mut hasher);
|
|
||||||
// SAFETY: u64 is valid for all bit patterns.
|
|
||||||
assert_eq!(hasher.finish(), unsafe { core::mem::transmute::<TypeId, u64>(type_id) });
|
|
||||||
}
|
|
||||||
// Pick a variety of types, just to demonstrate it’s all sane. Normal, zero-sized, unsized, &c.
|
|
||||||
verify_hashing_with(TypeId::of::<usize>());
|
|
||||||
verify_hashing_with(TypeId::of::<()>());
|
|
||||||
verify_hashing_with(TypeId::of::<str>());
|
|
||||||
verify_hashing_with(TypeId::of::<&str>());
|
|
||||||
verify_hashing_with(TypeId::of::<Vec<u8>>());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The raw, underlying form of a `Map`.
|
|
||||||
///
|
|
||||||
/// At its essence, this is a wrapper around `HashMap<TypeId, Box<Any>>`, with the portions that
|
|
||||||
/// would be memory-unsafe removed or marked unsafe. Normal people are expected to use the safe
|
|
||||||
/// `Map` interface instead, but there is the occasional use for this such as iteration over the
|
|
||||||
/// contents of an `Map`. However, because you will then be dealing with `Any` trait objects, it
|
|
||||||
/// doesn’t tend to be so very useful. Still, if you need it, it’s here.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RawMap<A: ?Sized + UncheckedAnyExt = dyn Any> {
|
|
||||||
inner: HashMap<TypeId, Box<A>, BuildHasherDefault<TypeIdHasher>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[derive(Clone)] would want A to implement Clone, but in reality it’s only Box<A> that can.
|
|
||||||
impl<A: ?Sized + UncheckedAnyExt> Clone for RawMap<A> where Box<A>: Clone {
|
|
||||||
#[inline]
|
|
||||||
fn clone(&self) -> RawMap<A> {
|
|
||||||
RawMap {
|
|
||||||
inner: self.inner.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_common_methods! {
|
|
||||||
field: RawMap.inner;
|
|
||||||
new() => HashMap::with_hasher(Default::default());
|
|
||||||
with_capacity(capacity) => HashMap::with_capacity_and_hasher(capacity, Default::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `RawMap` iterator.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Iter<'a, A: ?Sized + UncheckedAnyExt> {
|
|
||||||
inner: hash_map::Iter<'a, TypeId, Box<A>>,
|
|
||||||
}
|
|
||||||
impl<'a, A: ?Sized + UncheckedAnyExt> Iterator for Iter<'a, A> {
|
|
||||||
type Item = &'a A;
|
|
||||||
#[inline] fn next(&mut self) -> Option<&'a A> { self.inner.next().map(|x| &**x.1) }
|
|
||||||
#[inline] fn size_hint(&self) -> (usize, Option<usize>) { self.inner.size_hint() }
|
|
||||||
}
|
|
||||||
impl<'a, A: ?Sized + UncheckedAnyExt> ExactSizeIterator for Iter<'a, A> {
|
|
||||||
#[inline] fn len(&self) -> usize { self.inner.len() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `RawMap` mutable iterator.
|
|
||||||
pub struct IterMut<'a, A: ?Sized + UncheckedAnyExt> {
|
|
||||||
inner: hash_map::IterMut<'a, TypeId, Box<A>>,
|
|
||||||
}
|
|
||||||
impl<'a, A: ?Sized + UncheckedAnyExt> Iterator for IterMut<'a, A> {
|
|
||||||
type Item = &'a mut A;
|
|
||||||
#[inline] fn next(&mut self) -> Option<&'a mut A> { self.inner.next().map(|x| &mut **x.1) }
|
|
||||||
#[inline] fn size_hint(&self) -> (usize, Option<usize>) { self.inner.size_hint() }
|
|
||||||
}
|
|
||||||
impl<'a, A: ?Sized + UncheckedAnyExt> ExactSizeIterator for IterMut<'a, A> {
|
|
||||||
#[inline] fn len(&self) -> usize { self.inner.len() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `RawMap` move iterator.
|
|
||||||
pub struct IntoIter<A: ?Sized + UncheckedAnyExt> {
|
|
||||||
inner: hash_map::IntoIter<TypeId, Box<A>>,
|
|
||||||
}
|
|
||||||
impl<A: ?Sized + UncheckedAnyExt> Iterator for IntoIter<A> {
|
|
||||||
type Item = Box<A>;
|
|
||||||
#[inline] fn next(&mut self) -> Option<Box<A>> { self.inner.next().map(|x| x.1) }
|
|
||||||
#[inline] fn size_hint(&self) -> (usize, Option<usize>) { self.inner.size_hint() }
|
|
||||||
}
|
|
||||||
impl<A: ?Sized + UncheckedAnyExt> ExactSizeIterator for IntoIter<A> {
|
|
||||||
#[inline] fn len(&self) -> usize { self.inner.len() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `RawMap` drain iterator.
|
|
||||||
pub struct Drain<'a, A: ?Sized + UncheckedAnyExt> {
|
|
||||||
inner: hash_map::Drain<'a, TypeId, Box<A>>,
|
|
||||||
}
|
|
||||||
impl<'a, A: ?Sized + UncheckedAnyExt> Iterator for Drain<'a, A> {
|
|
||||||
type Item = Box<A>;
|
|
||||||
#[inline] fn next(&mut self) -> Option<Box<A>> { self.inner.next().map(|x| x.1) }
|
|
||||||
#[inline] fn size_hint(&self) -> (usize, Option<usize>) { self.inner.size_hint() }
|
|
||||||
}
|
|
||||||
impl<'a, A: ?Sized + UncheckedAnyExt> ExactSizeIterator for Drain<'a, A> {
|
|
||||||
#[inline] fn len(&self) -> usize { self.inner.len() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: ?Sized + UncheckedAnyExt> RawMap<A> {
|
|
||||||
/// An iterator visiting all entries in arbitrary order.
|
|
||||||
///
|
|
||||||
/// Iterator element type is `&Any`.
|
|
||||||
#[inline]
|
|
||||||
pub fn iter(&self) -> Iter<A> {
|
|
||||||
Iter {
|
|
||||||
inner: self.inner.iter(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An iterator visiting all entries in arbitrary order.
|
|
||||||
///
|
|
||||||
/// Iterator element type is `&mut Any`.
|
|
||||||
#[inline]
|
|
||||||
pub fn iter_mut(&mut self) -> IterMut<A> {
|
|
||||||
IterMut {
|
|
||||||
inner: self.inner.iter_mut(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears the map, returning all items as an iterator.
|
|
||||||
///
|
|
||||||
/// Iterator element type is `Box<Any>`.
|
|
||||||
///
|
|
||||||
/// Keeps the allocated memory for reuse.
|
|
||||||
#[inline]
|
|
||||||
pub fn drain(&mut self) -> Drain<A> {
|
|
||||||
Drain {
|
|
||||||
inner: self.inner.drain(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the entry for the given type in the collection for in-place manipulation.
|
|
||||||
#[inline]
|
|
||||||
pub fn entry(&mut self, key: TypeId) -> Entry<A> {
|
|
||||||
match self.inner.entry(key) {
|
|
||||||
hash_map::Entry::Occupied(e) => Entry::Occupied(OccupiedEntry {
|
|
||||||
inner: e,
|
|
||||||
}),
|
|
||||||
hash_map::Entry::Vacant(e) => Entry::Vacant(VacantEntry {
|
|
||||||
inner: e,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to the value corresponding to the 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 the key type.
|
|
||||||
#[inline]
|
|
||||||
pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&A>
|
|
||||||
where TypeId: Borrow<Q>, Q: Hash + Eq {
|
|
||||||
self.inner.get(k).map(|x| &**x)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 the key type.
|
|
||||||
#[inline]
|
|
||||||
pub fn contains_key<Q: ?Sized>(&self, k: &Q) -> bool
|
|
||||||
where TypeId: Borrow<Q>, Q: Hash + Eq {
|
|
||||||
self.inner.contains_key(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a mutable reference to the value corresponding to the 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 the key type.
|
|
||||||
#[inline]
|
|
||||||
pub fn get_mut<Q: ?Sized>(&mut self, k: &Q) -> Option<&mut A>
|
|
||||||
where TypeId: Borrow<Q>, Q: Hash + Eq {
|
|
||||||
self.inner.get_mut(k).map(|x| &mut **x)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inserts a key-value pair from the map. If the key already had a value present in the map,
|
|
||||||
/// that value is returned. Otherwise, `None` is returned.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// `key` and the type ID of `value` must match, or *undefined behaviour* occurs.
|
|
||||||
#[inline]
|
|
||||||
pub unsafe fn insert(&mut self, key: TypeId, value: Box<A>) -> Option<Box<A>> {
|
|
||||||
self.inner.insert(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes a key from the map, returning the value at the key if the key was previously in the
|
|
||||||
/// map.
|
|
||||||
///
|
|
||||||
/// 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 the key type.
|
|
||||||
#[inline]
|
|
||||||
pub fn remove<Q: ?Sized>(&mut self, k: &Q) -> Option<Box<A>>
|
|
||||||
where TypeId: Borrow<Q>, Q: Hash + Eq {
|
|
||||||
self.inner.remove(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: ?Sized + UncheckedAnyExt, Q> Index<Q> for RawMap<A> where TypeId: Borrow<Q>, Q: Eq + Hash {
|
|
||||||
type Output = A;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn index(&self, index: Q) -> &A {
|
|
||||||
self.get(&index).expect("no entry found for key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: ?Sized + UncheckedAnyExt, Q> IndexMut<Q> for RawMap<A> where TypeId: Borrow<Q>, Q: Eq + Hash {
|
|
||||||
#[inline]
|
|
||||||
fn index_mut(&mut self, index: Q) -> &mut A {
|
|
||||||
self.get_mut(&index).expect("no entry found for key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: ?Sized + UncheckedAnyExt> IntoIterator for RawMap<A> {
|
|
||||||
type Item = Box<A>;
|
|
||||||
type IntoIter = IntoIter<A>;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn into_iter(self) -> IntoIter<A> {
|
|
||||||
IntoIter {
|
|
||||||
inner: self.inner.into_iter(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A view into a single occupied location in a `RawMap`.
|
|
||||||
pub struct OccupiedEntry<'a, A: ?Sized + UncheckedAnyExt> {
|
|
||||||
#[cfg(all(feature = "std", not(feature = "hashbrown")))]
|
|
||||||
inner: hash_map::OccupiedEntry<'a, TypeId, Box<A>>,
|
|
||||||
#[cfg(feature = "hashbrown")]
|
|
||||||
inner: hash_map::OccupiedEntry<'a, TypeId, Box<A>, BuildHasherDefault<TypeIdHasher>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A view into a single empty location in a `RawMap`.
|
|
||||||
pub struct VacantEntry<'a, A: ?Sized + UncheckedAnyExt> {
|
|
||||||
#[cfg(all(feature = "std", not(feature = "hashbrown")))]
|
|
||||||
inner: hash_map::VacantEntry<'a, TypeId, Box<A>>,
|
|
||||||
#[cfg(feature = "hashbrown")]
|
|
||||||
inner: hash_map::VacantEntry<'a, TypeId, Box<A>, BuildHasherDefault<TypeIdHasher>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A view into a single location in a `RawMap`, which may be vacant or occupied.
|
|
||||||
pub enum Entry<'a, A: ?Sized + UncheckedAnyExt> {
|
|
||||||
/// An occupied Entry
|
|
||||||
Occupied(OccupiedEntry<'a, A>),
|
|
||||||
/// A vacant Entry
|
|
||||||
Vacant(VacantEntry<'a, A>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, A: ?Sized + UncheckedAnyExt> Entry<'a, A> {
|
|
||||||
/// Ensures a value is in the entry by inserting the default if empty, and returns
|
|
||||||
/// a mutable reference to the value in the entry.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The type ID of `default` must match the entry’s key, or *undefined behaviour* occurs.
|
|
||||||
#[inline]
|
|
||||||
pub unsafe fn or_insert(self, default: Box<A>) -> &'a mut A {
|
|
||||||
match self {
|
|
||||||
Entry::Occupied(inner) => inner.into_mut(),
|
|
||||||
Entry::Vacant(inner) => inner.insert(default),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ensures a value is in the entry by inserting the result of the default function if empty,
|
|
||||||
/// and returns a mutable reference to the value in the entry.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The type ID of the value returned by `default` must match the entry’s key,
|
|
||||||
/// or *undefined behaviour* occurs.
|
|
||||||
#[inline]
|
|
||||||
pub unsafe fn or_insert_with<F: FnOnce() -> Box<A>>(self, default: F) -> &'a mut A {
|
|
||||||
match self {
|
|
||||||
Entry::Occupied(inner) => inner.into_mut(),
|
|
||||||
Entry::Vacant(inner) => inner.insert(default()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, A: ?Sized + UncheckedAnyExt> OccupiedEntry<'a, A> {
|
|
||||||
/// Gets a reference to the value in the entry.
|
|
||||||
#[inline]
|
|
||||||
pub fn get(&self) -> &A {
|
|
||||||
&**self.inner.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets a mutable reference to the value in the entry.
|
|
||||||
#[inline]
|
|
||||||
pub fn get_mut(&mut self) -> &mut A {
|
|
||||||
&mut **self.inner.get_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts the OccupiedEntry into a mutable reference to the value in the entry
|
|
||||||
/// with a lifetime bound to the collection itself.
|
|
||||||
#[inline]
|
|
||||||
pub fn into_mut(self) -> &'a mut A {
|
|
||||||
&mut **self.inner.into_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the value of the entry, and returns the entry's old value.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The type ID of `value` must match the entry’s key, or *undefined behaviour* occurs.
|
|
||||||
#[inline]
|
|
||||||
pub unsafe fn insert(&mut self, value: Box<A>) -> Box<A> {
|
|
||||||
self.inner.insert(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Takes the value out of the entry, and returns it.
|
|
||||||
#[inline]
|
|
||||||
pub fn remove(self) -> Box<A> {
|
|
||||||
self.inner.remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, A: ?Sized + UncheckedAnyExt> VacantEntry<'a, A> {
|
|
||||||
/// Sets the value of the entry with the VacantEntry's key,
|
|
||||||
/// and returns a mutable reference to it.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// The type ID of `value` must match the entry’s key, or *undefined behaviour* occurs.
|
|
||||||
#[inline]
|
|
||||||
pub unsafe fn insert(self, value: Box<A>) -> &'a mut A {
|
|
||||||
&mut **self.inner.insert(value)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue