Resolve the std/hashbrown conflict situation
Big diff, but it’s mostly just whitespace changes; ignore whitespace and it’s much smaller, though still not as tiny as it could potentially be. Essentially, this just duplicates everything for the hashbrown variant. It’d be possible to use generic associated types to achieve this without the duplication, but that depends on currently-unstable features, and is probably slightly more painful to use anyway. I’ll keep the approach in mind for a possible version 2, but for now this is the pragmatic route.
This commit is contained in:
parent
e04b8b4d6e
commit
40e60cefd6
|
@ -7,6 +7,15 @@ being bigger than I’d earlier intended.
|
||||||
|
|
||||||
- Fixed the broken `Extend` implementation added in 1.0.0-beta.1.
|
- Fixed the broken `Extend` implementation added in 1.0.0-beta.1.
|
||||||
|
|
||||||
|
- Split the hashbrown implementation into a new module, `hashbrown`:
|
||||||
|
std and hashbrown can now coexist completely peacefully,
|
||||||
|
with `anymap::Map` being powered by `std::collections::hash_map`,
|
||||||
|
and `anymap::hashbrown::Map` being powered by `hashbrown::hash_map`.
|
||||||
|
The `raw_hash_map` alias, provided in 1.0.0-beta.1 because of the ambiguity
|
||||||
|
of what backed `anymap::Map`, is removed as superfluous and useless.
|
||||||
|
`RawMap` remains, despite not being *required*, as an ergonomic improvement.
|
||||||
|
With this, we’re back to proper completely additive Cargo features.
|
||||||
|
|
||||||
# 1.0.0-beta.1 (2022-01-25)
|
# 1.0.0-beta.1 (2022-01-25)
|
||||||
|
|
||||||
- Removed `anymap::any::Any` in favour of just plain `core::any::Any`, since its
|
- Removed `anymap::any::Any` in favour of just plain `core::any::Any`, since its
|
||||||
|
|
|
@ -11,6 +11,9 @@ categories = ["rust-patterns", "data-structures", "no-std"]
|
||||||
license = "BlueOak-1.0.0 OR MIT OR Apache-2.0"
|
license = "BlueOak-1.0.0 OR MIT OR Apache-2.0"
|
||||||
include = ["/README.md", "/COPYING", "/CHANGELOG.md", "/src"]
|
include = ["/README.md", "/COPYING", "/CHANGELOG.md", "/src"]
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
std = []
|
std = []
|
||||||
|
|
|
@ -41,22 +41,20 @@ assert_eq!(&*data.get::<Foo>().unwrap().str, "foot");
|
||||||
|
|
||||||
## Cargo features/dependencies/usage
|
## Cargo features/dependencies/usage
|
||||||
|
|
||||||
Typical Cargo.toml usage:
|
Typical Cargo.toml usage, providing `anymap::AnyMap` *et al.* backed by `std::collections::HashMap`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anymap = "1.0.0-beta.1"
|
anymap = "1.0.0-beta.1"
|
||||||
```
|
```
|
||||||
|
|
||||||
No-std usage, using `alloc` and the [hashbrown](https://rust-lang.github.io/hashbrown) crate instead of `std::collections::HashMap`:
|
No-std usage, providing `anymap::hashbrown::AnyMap` *et al.* (note the different path, required because Cargo features are additive) backed by `alloc` and the [hashbrown](https://rust-lang.github.io/hashbrown) crate:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anymap = { version = "1.0.0-beta.1", default-features = false, features = ["hashbrown"] }
|
anymap = { version = "1.0.0-beta.1", default-features = false, features = ["hashbrown"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
The `std` feature is enabled by default. The `hashbrown` feature overrides it. At least one of the two must be enabled.
|
|
||||||
|
|
||||||
**On stability:** hashbrown is still pre-1.0.0 and experiencing breaking changes. Because it’s useful for a small fraction of users, I am retaining it, but with *different compatibility guarantees to the typical SemVer ones*. Where possible, I will just widen the range for new releases of hashbrown, but if an incompatible change occurs, I may drop support for older versions of hashbrown with a bump to the *minor* part of the anymap version number (e.g. 1.1.0, 1.2.0). Iff you’re using this feature, this is cause to *consider* using a tilde requirement like `"~1.0"` (or spell it out as `>=1, <1.1`).
|
**On stability:** hashbrown is still pre-1.0.0 and experiencing breaking changes. Because it’s useful for a small fraction of users, I am retaining it, but with *different compatibility guarantees to the typical SemVer ones*. Where possible, I will just widen the range for new releases of hashbrown, but if an incompatible change occurs, I may drop support for older versions of hashbrown with a bump to the *minor* part of the anymap version number (e.g. 1.1.0, 1.2.0). Iff you’re using this feature, this is cause to *consider* using a tilde requirement like `"~1.0"` (or spell it out as `>=1, <1.1`).
|
||||||
|
|
||||||
## Unsafe code in this library
|
## Unsafe code in this library
|
||||||
|
|
435
src/lib.rs
435
src/lib.rs
|
@ -1,142 +1,132 @@
|
||||||
//! This crate provides a safe and convenient store for one value of each type.
|
//! This crate provides a safe and convenient store for one value of each type.
|
||||||
//!
|
//!
|
||||||
//! Your starting point is [`Map`]. It has an example.
|
//! Your starting point is [`Map`]. It has an example.
|
||||||
|
//!
|
||||||
|
//! # Cargo features
|
||||||
|
//!
|
||||||
|
//! This crate has two independent features, each of which provides an implementation providing
|
||||||
|
//! types `Map`, `AnyMap`, `OccupiedEntry`, `VacantEntry`, `Entry` and `RawMap`:
|
||||||
|
//!
|
||||||
|
#![cfg_attr(feature = "std", doc = " - **std** (default, *enabled* in this build):")]
|
||||||
|
#![cfg_attr(not(feature = "std"), doc = " - **std** (default, *disabled* in this build):")]
|
||||||
|
//! an implementation using `std::collections::hash_map`, placed in the crate root
|
||||||
|
//! (e.g. `anymap::AnyMap`).
|
||||||
|
//!
|
||||||
|
#![cfg_attr(feature = "hashbrown", doc = " - **hashbrown** (optional; *enabled* in this build):")]
|
||||||
|
#![cfg_attr(not(feature = "hashbrown"), doc = " - **hashbrown** (optional; *disabled* in this build):")]
|
||||||
|
//! an implementation using `alloc` and `hashbrown::hash_map`, placed in a module `hashbrown`
|
||||||
|
//! (e.g. `anymap::hashbrown::AnyMap`).
|
||||||
|
|
||||||
#![warn(missing_docs, unused_results)]
|
#![warn(missing_docs, unused_results)]
|
||||||
|
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
use core::any::{Any, TypeId};
|
|
||||||
use core::convert::TryInto;
|
use core::convert::TryInto;
|
||||||
use core::hash::{Hasher, BuildHasherDefault};
|
use core::hash::Hasher;
|
||||||
use core::marker::PhantomData;
|
|
||||||
|
|
||||||
#[cfg(not(any(feature = "std", feature = "hashbrown")))]
|
|
||||||
compile_error!("anymap: you must enable the 'std' feature or the 'hashbrown' feature");
|
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
pub use crate::any::CloneAny;
|
||||||
use alloc::boxed::Box;
|
|
||||||
|
|
||||||
use any::{Downcast, IntoBox};
|
|
||||||
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;
|
|
||||||
|
|
||||||
mod any;
|
mod any;
|
||||||
|
|
||||||
/// Raw access to the underlying `HashMap`.
|
#[cfg(any(feature = "std", feature = "hashbrown"))]
|
||||||
///
|
macro_rules! everything {
|
||||||
/// This is a public type alias because the underlying `HashMap` could be
|
($example_init:literal, $($parent:ident)::+ $(, $entry_generics:ty)?) => {
|
||||||
/// `std::collections::HashMap` or `hashbrown::HashMap`, depending on the crate features enabled.
|
use core::any::{Any, TypeId};
|
||||||
/// For that reason, you should refer to this type as `anymap::RawMap` rather than
|
use core::hash::BuildHasherDefault;
|
||||||
/// `std::collections::HashMap` to avoid breakage if something else in your crate tree enables
|
use core::marker::PhantomData;
|
||||||
/// 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,
|
#[cfg(not(feature = "std"))]
|
||||||
/// type-safe access to those values.
|
use alloc::boxed::Box;
|
||||||
///
|
|
||||||
/// The type parameter `A` allows you to use a different value type; normally you will want it to
|
use ::$($parent)::+::hash_map::{self, HashMap};
|
||||||
/// be `core::any::Any` (also known as `std::any::Any`), but there are other choices:
|
|
||||||
///
|
use crate::any::{Downcast, IntoBox};
|
||||||
/// - If you want the entire map to be cloneable, use `CloneAny` instead of `Any`; with that, you
|
|
||||||
/// can only add types that implement `Clone` to the map.
|
/// Raw access to the underlying `HashMap`.
|
||||||
/// - You can add on `+ Send` or `+ Send + Sync` (e.g. `Map<dyn Any + Send>`) to add those auto
|
///
|
||||||
/// traits.
|
/// This alias is provided for convenience because of the ugly third generic parameter.
|
||||||
///
|
pub type RawMap<A> = HashMap<TypeId, Box<A>, BuildHasherDefault<TypeIdHasher>>;
|
||||||
/// Cumulatively, there are thus six forms of map:
|
|
||||||
///
|
/// A collection containing zero or one values for any given type and allowing convenient,
|
||||||
/// - <code>[Map]<dyn [core::any::Any]></code>, also spelled [`AnyMap`] for convenience.
|
/// type-safe access to those values.
|
||||||
/// - <code>[Map]<dyn [core::any::Any] + Send></code>
|
///
|
||||||
/// - <code>[Map]<dyn [core::any::Any] + Send + Sync></code>
|
/// The type parameter `A` allows you to use a different value type; normally you will want
|
||||||
/// - <code>[Map]<dyn [CloneAny]></code>
|
/// it to be `core::any::Any` (also known as `std::any::Any`), but there are other choices:
|
||||||
/// - <code>[Map]<dyn [CloneAny] + Send></code>
|
///
|
||||||
/// - <code>[Map]<dyn [CloneAny] + Send + Sync></code>
|
/// - If you want the entire map to be cloneable, use `CloneAny` instead of `Any`; with
|
||||||
///
|
/// that, you can only add types that implement `Clone` to the map.
|
||||||
/// ## Example
|
/// - You can add on `+ Send` or `+ Send + Sync` (e.g. `Map<dyn Any + Send>`) to add those
|
||||||
///
|
/// auto traits.
|
||||||
/// (Here using the [`AnyMap`] convenience alias; the first line could use
|
///
|
||||||
/// <code>[anymap::Map][Map]::<[core::any::Any]>::new()</code> instead if desired.)
|
/// Cumulatively, there are thus six forms of map:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// - <code>[Map]<dyn [core::any::Any]></code>,
|
||||||
/// let mut data = anymap::AnyMap::new();
|
/// also spelled [`AnyMap`] for convenience.
|
||||||
/// assert_eq!(data.get(), None::<&i32>);
|
/// - <code>[Map]<dyn [core::any::Any] + Send></code>
|
||||||
/// data.insert(42i32);
|
/// - <code>[Map]<dyn [core::any::Any] + Send + Sync></code>
|
||||||
/// assert_eq!(data.get(), Some(&42i32));
|
/// - <code>[Map]<dyn [CloneAny]></code>
|
||||||
/// data.remove::<i32>();
|
/// - <code>[Map]<dyn [CloneAny] + Send></code>
|
||||||
/// assert_eq!(data.get::<i32>(), None);
|
/// - <code>[Map]<dyn [CloneAny] + Send + Sync></code>
|
||||||
///
|
///
|
||||||
/// #[derive(Clone, PartialEq, Debug)]
|
/// ## Example
|
||||||
/// struct Foo {
|
///
|
||||||
/// str: String,
|
/// (Here using the [`AnyMap`] convenience alias; the first line could use
|
||||||
/// }
|
/// <code>[anymap::Map][Map]::<[core::any::Any]>::new()</code> instead if desired.)
|
||||||
///
|
///
|
||||||
/// assert_eq!(data.get::<Foo>(), None);
|
/// ```rust
|
||||||
/// data.insert(Foo { str: format!("foo") });
|
#[doc = $example_init]
|
||||||
/// assert_eq!(data.get(), Some(&Foo { str: format!("foo") }));
|
/// assert_eq!(data.get(), None::<&i32>);
|
||||||
/// data.get_mut::<Foo>().map(|foo| foo.str.push('t'));
|
/// data.insert(42i32);
|
||||||
/// assert_eq!(&*data.get::<Foo>().unwrap().str, "foot");
|
/// assert_eq!(data.get(), Some(&42i32));
|
||||||
/// ```
|
/// data.remove::<i32>();
|
||||||
///
|
/// assert_eq!(data.get::<i32>(), None);
|
||||||
/// Values containing non-static references are not permitted.
|
///
|
||||||
#[derive(Debug)]
|
/// #[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct Map<A: ?Sized + Downcast = dyn Any> {
|
/// struct Foo {
|
||||||
|
/// str: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// assert_eq!(data.get::<Foo>(), None);
|
||||||
|
/// data.insert(Foo { str: format!("foo") });
|
||||||
|
/// assert_eq!(data.get(), Some(&Foo { str: format!("foo") }));
|
||||||
|
/// data.get_mut::<Foo>().map(|foo| foo.str.push('t'));
|
||||||
|
/// assert_eq!(&*data.get::<Foo>().unwrap().str, "foot");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Values containing non-static references are not permitted.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Map<A: ?Sized + Downcast = dyn Any> {
|
||||||
raw: RawMap<A>,
|
raw: RawMap<A>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[derive(Clone)] would want A to implement Clone, but in reality it’s only Box<A> that can.
|
// #[derive(Clone)] would want A to implement Clone, but in reality only Box<A> can.
|
||||||
impl<A: ?Sized + Downcast> Clone for Map<A> where Box<A>: Clone {
|
impl<A: ?Sized + Downcast> Clone for Map<A> where Box<A>: Clone {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn clone(&self) -> Map<A> {
|
fn clone(&self) -> Map<A> {
|
||||||
Map {
|
Map {
|
||||||
raw: self.raw.clone(),
|
raw: self.raw.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The most common type of `Map`: just using `Any`; <code>[Map]<dyn [Any]></code>.
|
/// The most common type of `Map`: just using `Any`; <code>[Map]<dyn [Any]></code>.
|
||||||
///
|
///
|
||||||
/// Why is this a separate type alias rather than a default value for `Map<A>`? `Map::new()`
|
/// Why is this a separate type alias rather than a default value for `Map<A>`?
|
||||||
/// doesn’t seem to be happy to infer that it should go with the default value.
|
/// `Map::new()` doesn’t seem to be happy to infer that it should go with the default
|
||||||
/// It’s a bit sad, really. Ah well, I guess this approach will do.
|
/// value. It’s a bit sad, really. Ah well, I guess this approach will do.
|
||||||
pub type AnyMap = Map<dyn Any>;
|
pub type AnyMap = Map<dyn Any>;
|
||||||
|
|
||||||
impl<A: ?Sized + Downcast> Default for Map<A> {
|
impl<A: ?Sized + Downcast> Default for Map<A> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn default() -> Map<A> {
|
fn default() -> Map<A> {
|
||||||
Map::new()
|
Map::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: ?Sized + Downcast> Map<A> {
|
impl<A: ?Sized + Downcast> Map<A> {
|
||||||
/// Create an empty collection.
|
/// Create an empty collection.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new() -> Map<A> {
|
pub fn new() -> Map<A> {
|
||||||
|
@ -201,7 +191,8 @@ impl<A: ?Sized + Downcast> Map<A> {
|
||||||
self.raw.clear()
|
self.raw.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the value stored in the collection for the type `T`, if it exists.
|
/// Returns a reference to the value stored in the collection for the type `T`,
|
||||||
|
/// if it exists.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get<T: IntoBox<A>>(&self) -> Option<&T> {
|
pub fn get<T: IntoBox<A>>(&self) -> Option<&T> {
|
||||||
self.raw.get(&TypeId::of::<T>())
|
self.raw.get(&TypeId::of::<T>())
|
||||||
|
@ -225,7 +216,7 @@ impl<A: ?Sized + Downcast> Map<A> {
|
||||||
.map(|any| unsafe { *any.downcast_unchecked::<T>() })
|
.map(|any| unsafe { *any.downcast_unchecked::<T>() })
|
||||||
}
|
}
|
||||||
|
|
||||||
// rustc 1.60.0-nightly has another method try_insert that would be nice to add when stable.
|
// rustc 1.60.0-nightly has another method try_insert that would be nice when stable.
|
||||||
|
|
||||||
/// Removes the `T` value from the collection,
|
/// Removes the `T` value from the collection,
|
||||||
/// returning it if there was one or `None` if there was not.
|
/// returning it if there was one or `None` if there was not.
|
||||||
|
@ -245,11 +236,11 @@ impl<A: ?Sized + Downcast> 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_hash_map::Entry::Occupied(e) => Entry::Occupied(OccupiedEntry {
|
hash_map::Entry::Occupied(e) => Entry::Occupied(OccupiedEntry {
|
||||||
inner: e,
|
inner: e,
|
||||||
type_: PhantomData,
|
type_: PhantomData,
|
||||||
}),
|
}),
|
||||||
raw_hash_map::Entry::Vacant(e) => Entry::Vacant(VacantEntry {
|
hash_map::Entry::Vacant(e) => Entry::Vacant(VacantEntry {
|
||||||
inner: e,
|
inner: e,
|
||||||
type_: PhantomData,
|
type_: PhantomData,
|
||||||
}),
|
}),
|
||||||
|
@ -258,14 +249,8 @@ impl<A: ?Sized + Downcast> Map<A> {
|
||||||
|
|
||||||
/// Get access to the raw hash map that backs this.
|
/// 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
|
/// This will seldom be useful, but it’s conceivable that you could wish to iterate
|
||||||
/// the items in the collection, and this lets you do that.
|
/// 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]
|
#[inline]
|
||||||
pub fn as_raw(&self) -> &RawMap<A> {
|
pub fn as_raw(&self) -> &RawMap<A> {
|
||||||
&self.raw
|
&self.raw
|
||||||
|
@ -273,20 +258,14 @@ impl<A: ?Sized + Downcast> Map<A> {
|
||||||
|
|
||||||
/// Get mutable access to the raw hash map that backs this.
|
/// 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
|
/// This will seldom be useful, but it’s conceivable that you could wish to iterate
|
||||||
/// the items in the collection mutably, or drain or something, or *possibly* even batch
|
/// over all the items in the collection mutably, or drain or something, or *possibly*
|
||||||
/// insert, and this lets you do that.
|
/// 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
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// If you insert any values to the raw map, the key (a `TypeId`) must match the value’s type,
|
/// If you insert any values to the raw map, the key (a `TypeId`) must match the
|
||||||
/// or *undefined behaviour* will occur when you access those values.
|
/// value’s type, or *undefined behaviour* will occur when you access those values.
|
||||||
///
|
///
|
||||||
/// (*Removing* entries is perfectly safe.)
|
/// (*Removing* entries is perfectly safe.)
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -296,15 +275,9 @@ impl<A: ?Sized + Downcast> Map<A> {
|
||||||
|
|
||||||
/// Convert this into the raw hash map that backs this.
|
/// 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
|
/// This will seldom be useful, but it’s conceivable that you could wish to consume all
|
||||||
/// items in the collection and do *something* with some or all of them, and this lets you do
|
/// the items in the collection and do *something* with some or all of them, and this
|
||||||
/// that, without the `unsafe` that `.as_raw_mut().drain()` would require.
|
/// 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]
|
#[inline]
|
||||||
pub fn into_raw(self) -> RawMap<A> {
|
pub fn into_raw(self) -> RawMap<A> {
|
||||||
self.raw
|
self.raw
|
||||||
|
@ -312,19 +285,14 @@ impl<A: ?Sized + Downcast> Map<A> {
|
||||||
|
|
||||||
/// Construct a map from a collection of raw values.
|
/// 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
|
/// You know what? I can’t immediately think of any legitimate use for this, especially
|
||||||
/// of the requirement of the `BuildHasherDefault<TypeIdHasher>` generic in the map.
|
/// 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
|
/// Perhaps this will be most practical as `unsafe { Map::from_raw(iter.collect()) }`,
|
||||||
/// being an iterator over `(TypeId, Box<A>)` pairs. Eh, this method provides symmetry with
|
/// `iter` being an iterator over `(TypeId, Box<A>)` pairs. Eh, this method provides
|
||||||
/// `into_raw`, so I don’t care if literally no one ever uses it. I’m not even going to write a
|
/// symmetry with `into_raw`, so I don’t care if literally no one ever uses it. I’m not
|
||||||
/// test for it, it’s so trivial.
|
/// 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
|
/// # Safety
|
||||||
///
|
///
|
||||||
|
@ -334,44 +302,38 @@ impl<A: ?Sized + Downcast> Map<A> {
|
||||||
pub unsafe fn from_raw(raw: RawMap<A>) -> Map<A> {
|
pub unsafe fn from_raw(raw: RawMap<A>) -> Map<A> {
|
||||||
Self { raw }
|
Self { raw }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: ?Sized + Downcast> Extend<Box<A>> for Map<A> {
|
impl<A: ?Sized + Downcast> 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 _ = self.raw.insert(Downcast::type_id(&*item), item);
|
let _ = self.raw.insert(Downcast::type_id(&*item), item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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 + Downcast, V: 'a> {
|
pub struct OccupiedEntry<'a, A: ?Sized + Downcast, V: 'a> {
|
||||||
#[cfg(all(feature = "std", not(feature = "hashbrown")))]
|
inner: hash_map::OccupiedEntry<'a, TypeId, Box<A>, $($entry_generics)?>,
|
||||||
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 + Downcast, V: 'a> {
|
pub struct VacantEntry<'a, A: ?Sized + Downcast, V: 'a> {
|
||||||
#[cfg(all(feature = "std", not(feature = "hashbrown")))]
|
inner: hash_map::VacantEntry<'a, TypeId, Box<A>, $($entry_generics)?>,
|
||||||
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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A view into a single location in an `Map`, which may be vacant or occupied.
|
/// A view into a single location in an `Map`, which may be vacant or occupied.
|
||||||
pub enum Entry<'a, A: ?Sized + Downcast, V: 'a> {
|
pub enum Entry<'a, A: ?Sized + Downcast, V: 'a> {
|
||||||
/// An occupied Entry
|
/// An occupied Entry
|
||||||
Occupied(OccupiedEntry<'a, A, V>),
|
Occupied(OccupiedEntry<'a, A, V>),
|
||||||
/// A vacant Entry
|
/// A vacant Entry
|
||||||
Vacant(VacantEntry<'a, A, V>),
|
Vacant(VacantEntry<'a, A, V>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> Entry<'a, A, V> {
|
impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> Entry<'a, A, V> {
|
||||||
/// Ensures a value is in the entry by inserting the default if empty, and returns
|
/// Ensures a value is in the entry by inserting the default if empty, and returns
|
||||||
/// a mutable reference to the value in the entry.
|
/// a mutable reference to the value in the entry.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -382,8 +344,8 @@ impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> Entry<'a, A, V> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensures a value is in the entry by inserting the result of the default function if empty,
|
/// Ensures a value is in the entry by inserting the result of the default function if
|
||||||
/// and returns a mutable reference to the value in the entry.
|
/// empty, and returns a mutable reference to the value in the entry.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'a mut V {
|
pub fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'a mut V {
|
||||||
match self {
|
match self {
|
||||||
|
@ -402,8 +364,8 @@ impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> Entry<'a, A, V> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides in-place mutable access to an occupied entry before any potential inserts into the
|
/// Provides in-place mutable access to an occupied entry before any potential inserts
|
||||||
/// map.
|
/// into the map.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn and_modify<F: FnOnce(&mut V)>(self, f: F) -> Self {
|
pub fn and_modify<F: FnOnce(&mut V)>(self, f: F) -> Self {
|
||||||
match self {
|
match self {
|
||||||
|
@ -417,9 +379,9 @@ impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> Entry<'a, A, V> {
|
||||||
|
|
||||||
// Additional stable methods (as of 1.60.0-nightly) that could be added:
|
// Additional stable methods (as of 1.60.0-nightly) that could be added:
|
||||||
// insert_entry(self, value: V) -> OccupiedEntry<'a, K, V> (1.59.0)
|
// insert_entry(self, value: V) -> OccupiedEntry<'a, K, V> (1.59.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> OccupiedEntry<'a, A, V> {
|
impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> OccupiedEntry<'a, A, V> {
|
||||||
/// Gets a reference to the value in the entry
|
/// Gets a reference to the value in the entry
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get(&self) -> &V {
|
pub fn get(&self) -> &V {
|
||||||
|
@ -450,48 +412,20 @@ impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> OccupiedEntry<'a, A, V> {
|
||||||
pub fn remove(self) -> V {
|
pub fn remove(self) -> V {
|
||||||
unsafe { *self.inner.remove().downcast_unchecked() }
|
unsafe { *self.inner.remove().downcast_unchecked() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> VacantEntry<'a, A, V> {
|
impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> VacantEntry<'a, A, V> {
|
||||||
/// Sets the value of the entry with the VacantEntry's key,
|
/// Sets the value of the entry with the VacantEntry's key,
|
||||||
/// and returns a mutable reference to it
|
/// and returns a mutable reference to it
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn insert(self, value: V) -> &'a mut V {
|
pub fn insert(self, value: V) -> &'a mut V {
|
||||||
unsafe { self.inner.insert(value.into_box()).downcast_mut_unchecked() }
|
unsafe { self.inner.insert(value.into_box()).downcast_mut_unchecked() }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// 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]
|
#[cfg(test)]
|
||||||
fn finish(&self) -> u64 { self.value }
|
mod tests {
|
||||||
}
|
use crate::CloneAny;
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)] struct A(i32);
|
#[derive(Clone, Debug, PartialEq)] struct A(i32);
|
||||||
|
@ -626,10 +560,77 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn type_id_hasher() {
|
fn test_extend() {
|
||||||
|
let mut map = AnyMap::new();
|
||||||
|
// (vec![] for 1.36.0 compatibility; more recently, you should use [] instead.)
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
use alloc::vec;
|
||||||
|
map.extend(vec![Box::new(123) as Box<dyn Any>, Box::new(456), Box::new(true)]);
|
||||||
|
assert_eq!(map.get(), Some(&456));
|
||||||
|
assert_eq!(map.get::<bool>(), Some(&true));
|
||||||
|
assert!(map.get::<Box<dyn Any>>().is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
everything!(
|
||||||
|
"let mut data = anymap::AnyMap::new();",
|
||||||
|
std::collections
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "hashbrown")]
|
||||||
|
/// AnyMap backed by `hashbrown`.
|
||||||
|
///
|
||||||
|
/// This depends on the `hashbrown` Cargo feature being enabled.
|
||||||
|
pub mod hashbrown {
|
||||||
|
use crate::TypeIdHasher;
|
||||||
|
#[cfg(doc)]
|
||||||
|
use crate::any::CloneAny;
|
||||||
|
|
||||||
|
everything!(
|
||||||
|
"let mut data = anymap::hashbrown::AnyMap::new();",
|
||||||
|
hashbrown,
|
||||||
|
BuildHasherDefault<TypeIdHasher>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_id_hasher() {
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::hash::Hash;
|
use core::hash::Hash;
|
||||||
|
use core::any::TypeId;
|
||||||
fn verify_hashing_with(type_id: TypeId) {
|
fn verify_hashing_with(type_id: TypeId) {
|
||||||
let mut hasher = TypeIdHasher::default();
|
let mut hasher = TypeIdHasher::default();
|
||||||
type_id.hash(&mut hasher);
|
type_id.hash(&mut hasher);
|
||||||
|
@ -642,14 +643,4 @@ mod tests {
|
||||||
verify_hashing_with(TypeId::of::<str>());
|
verify_hashing_with(TypeId::of::<str>());
|
||||||
verify_hashing_with(TypeId::of::<&str>());
|
verify_hashing_with(TypeId::of::<&str>());
|
||||||
verify_hashing_with(TypeId::of::<Vec<u8>>());
|
verify_hashing_with(TypeId::of::<Vec<u8>>());
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_extend() {
|
|
||||||
let mut map = AnyMap::new();
|
|
||||||
map.extend([Box::new(123) as Box<dyn Any>, Box::new(456), Box::new(true)]);
|
|
||||||
assert_eq!(map.get(), Some(&456));
|
|
||||||
assert_eq!(map.get::<bool>(), Some(&true));
|
|
||||||
assert!(map.get::<Box<dyn Any>>().is_none());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
5
test
5
test
|
@ -4,11 +4,10 @@ export RUSTFLAGS="-D warnings"
|
||||||
export RUSTDOCFLAGS="-D warnings"
|
export RUSTDOCFLAGS="-D warnings"
|
||||||
run_tests() {
|
run_tests() {
|
||||||
for release in "" "--release"; do
|
for release in "" "--release"; do
|
||||||
|
cargo $1 test $release --no-default-features # Not very useful without std or hashbrown, but hey, it works! (Doctests emit an error about needing a global allocator, but it exits zero anyway. ¯\_(ツ)_/¯)
|
||||||
cargo $1 test $release --no-default-features --features hashbrown
|
cargo $1 test $release --no-default-features --features hashbrown
|
||||||
cargo $1 test $release --features hashbrown
|
|
||||||
# (2>/dev/null because otherwise you’ll keep seeing errors and double-guessing whether they were supposed to happen or whether the script failed to exit nonzero.)
|
|
||||||
! 2>/dev/null cargo $1 test $release --no-default-features || ! echo "'cargo $1 test $release --no-default-features' failed to fail (sorry, its stderr is suppressed, try it manually)"
|
|
||||||
cargo $1 test $release
|
cargo $1 test $release
|
||||||
|
cargo $1 test $release --all-features
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue