From: Chris Morgan Date: Tue, 22 Feb 2022 02:50:22 +0000 (+1100) Subject: Resolve the std/hashbrown conflict situation X-Git-Tag: 1.0.0-beta.2~1 X-Git-Url: https://git.chrismorgan.info/anymap/commitdiff_plain/40e60cefd62236753888e887713692713683cdce 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. --- diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dd437e..5fad564 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ being bigger than I’d earlier intended. - 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) - Removed `anymap::any::Any` in favour of just plain `core::any::Any`, since its diff --git a/Cargo.toml b/Cargo.toml index 02d16c7..aca1a64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,9 @@ categories = ["rust-patterns", "data-structures", "no-std"] license = "BlueOak-1.0.0 OR MIT OR Apache-2.0" include = ["/README.md", "/COPYING", "/CHANGELOG.md", "/src"] +[package.metadata.docs.rs] +all-features = true + [features] default = ["std"] std = [] diff --git a/README.md b/README.md index 798f925..d0af879 100644 --- a/README.md +++ b/README.md @@ -41,22 +41,20 @@ assert_eq!(&*data.get::().unwrap().str, "foot"); ## Cargo features/dependencies/usage -Typical Cargo.toml usage: +Typical Cargo.toml usage, providing `anymap::AnyMap` *et al.* backed by `std::collections::HashMap`: ```toml [dependencies] 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 [dependencies] 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`). ## Unsafe code in this library diff --git a/src/lib.rs b/src/lib.rs index 560a73d..9c57f09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,464 +1,599 @@ //! This crate provides a safe and convenient store for one value of each type. //! //! 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)] #![cfg_attr(not(feature = "std"), no_std)] -use core::any::{Any, TypeId}; use core::convert::TryInto; -use core::hash::{Hasher, BuildHasherDefault}; -use core::marker::PhantomData; - -#[cfg(not(any(feature = "std", feature = "hashbrown")))] -compile_error!("anymap: you must enable the 'std' feature or the 'hashbrown' feature"); +use core::hash::Hasher; #[cfg(not(feature = "std"))] extern crate alloc; -#[cfg(not(feature = "std"))] -use alloc::boxed::Box; - -use any::{Downcast, IntoBox}; -pub use any::CloneAny; +pub use crate::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; +mod any; -#[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; +#[cfg(any(feature = "std", feature = "hashbrown"))] +macro_rules! everything { + ($example_init:literal, $($parent:ident)::+ $(, $entry_generics:ty)?) => { + use core::any::{Any, TypeId}; + use core::hash::BuildHasherDefault; + use core::marker::PhantomData; -use self::raw_hash_map::HashMap; + #[cfg(not(feature = "std"))] + use alloc::boxed::Box; + + use ::$($parent)::+::hash_map::{self, HashMap}; + + use crate::any::{Downcast, IntoBox}; + + /// Raw access to the underlying `HashMap`. + /// + /// This alias is provided for convenience because of the ugly third generic parameter. + pub type RawMap = HashMap, BuildHasherDefault>; + + /// A collection containing zero or one values for any given type and allowing convenient, + /// type-safe access to those values. + /// + /// The type parameter `A` allows you to use a different value type; normally you will want + /// it to be `core::any::Any` (also known as `std::any::Any`), but there are other choices: + /// + /// - 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. + /// - You can add on `+ Send` or `+ Send + Sync` (e.g. `Map`) to add those + /// auto traits. + /// + /// Cumulatively, there are thus six forms of map: + /// + /// - [Map]<dyn [core::any::Any]>, + /// also spelled [`AnyMap`] for convenience. + /// - [Map]<dyn [core::any::Any] + Send> + /// - [Map]<dyn [core::any::Any] + Send + Sync> + /// - [Map]<dyn [CloneAny]> + /// - [Map]<dyn [CloneAny] + Send> + /// - [Map]<dyn [CloneAny] + Send + Sync> + /// + /// ## Example + /// + /// (Here using the [`AnyMap`] convenience alias; the first line could use + /// [anymap::Map][Map]::<[core::any::Any]>::new() instead if desired.) + /// + /// ```rust + #[doc = $example_init] + /// assert_eq!(data.get(), None::<&i32>); + /// data.insert(42i32); + /// assert_eq!(data.get(), Some(&42i32)); + /// data.remove::(); + /// assert_eq!(data.get::(), None); + /// + /// #[derive(Clone, PartialEq, Debug)] + /// struct Foo { + /// str: String, + /// } + /// + /// assert_eq!(data.get::(), None); + /// data.insert(Foo { str: format!("foo") }); + /// assert_eq!(data.get(), Some(&Foo { str: format!("foo") })); + /// data.get_mut::().map(|foo| foo.str.push('t')); + /// assert_eq!(&*data.get::().unwrap().str, "foot"); + /// ``` + /// + /// Values containing non-static references are not permitted. + #[derive(Debug)] + pub struct Map { + raw: RawMap, + } -mod any; + // #[derive(Clone)] would want A to implement Clone, but in reality only Box can. + impl Clone for Map where Box: Clone { + #[inline] + fn clone(&self) -> Map { + Map { + raw: self.raw.clone(), + } + } + } -/// 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 = HashMap, BuildHasherDefault>; + /// The most common type of `Map`: just using `Any`; [Map]<dyn [Any]>. + /// + /// Why is this a separate type alias rather than a default value for `Map`? + /// `Map::new()` doesn’t seem to be happy to infer that it should go with the default + /// value. It’s a bit sad, really. Ah well, I guess this approach will do. + pub type AnyMap = Map; + + impl Default for Map { + #[inline] + fn default() -> Map { + Map::new() + } + } -/// A collection containing zero or one values for any given type and allowing convenient, -/// type-safe access to those values. -/// -/// The type parameter `A` allows you to use a different value type; normally you will want it to -/// be `core::any::Any` (also known as `std::any::Any`), but there are other choices: -/// -/// - 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. -/// - You can add on `+ Send` or `+ Send + Sync` (e.g. `Map`) to add those auto -/// traits. -/// -/// Cumulatively, there are thus six forms of map: -/// -/// - [Map]<dyn [core::any::Any]>, also spelled [`AnyMap`] for convenience. -/// - [Map]<dyn [core::any::Any] + Send> -/// - [Map]<dyn [core::any::Any] + Send + Sync> -/// - [Map]<dyn [CloneAny]> -/// - [Map]<dyn [CloneAny] + Send> -/// - [Map]<dyn [CloneAny] + Send + Sync> -/// -/// ## Example -/// -/// (Here using the [`AnyMap`] convenience alias; the first line could use -/// [anymap::Map][Map]::<[core::any::Any]>::new() instead if desired.) -/// -/// ```rust -/// let mut data = anymap::AnyMap::new(); -/// assert_eq!(data.get(), None::<&i32>); -/// data.insert(42i32); -/// assert_eq!(data.get(), Some(&42i32)); -/// data.remove::(); -/// assert_eq!(data.get::(), None); -/// -/// #[derive(Clone, PartialEq, Debug)] -/// struct Foo { -/// str: String, -/// } -/// -/// assert_eq!(data.get::(), None); -/// data.insert(Foo { str: format!("foo") }); -/// assert_eq!(data.get(), Some(&Foo { str: format!("foo") })); -/// data.get_mut::().map(|foo| foo.str.push('t')); -/// assert_eq!(&*data.get::().unwrap().str, "foot"); -/// ``` -/// -/// Values containing non-static references are not permitted. -#[derive(Debug)] -pub struct Map { - raw: RawMap, -} + impl Map { + /// Create an empty collection. + #[inline] + pub fn new() -> Map { + Map { + raw: RawMap::with_hasher(Default::default()), + } + } -// #[derive(Clone)] would want A to implement Clone, but in reality it’s only Box that can. -impl Clone for Map where Box: Clone { - #[inline] - fn clone(&self) -> Map { - Map { - raw: self.raw.clone(), - } - } -} + /// Creates an empty collection with the given initial capacity. + #[inline] + pub fn with_capacity(capacity: usize) -> Map { + Map { + raw: RawMap::with_capacity_and_hasher(capacity, Default::default()), + } + } -/// The most common type of `Map`: just using `Any`; [Map]<dyn [Any]>. -/// -/// Why is this a separate type alias rather than a default value for `Map`? `Map::new()` -/// doesn’t seem to be happy to infer that it should go with the default value. -/// It’s a bit sad, really. Ah well, I guess this approach will do. -pub type AnyMap = Map; + /// Returns the number of elements the collection can hold without reallocating. + #[inline] + pub fn capacity(&self) -> usize { + self.raw.capacity() + } -impl Default for Map { - #[inline] - fn default() -> Map { - Map::new() - } -} + /// Reserves capacity for at least `additional` more elements to be inserted + /// in the collection. The collection may reserve more space to avoid + /// frequent reallocations. + /// + /// # Panics + /// + /// Panics if the new allocation size overflows `usize`. + #[inline] + pub fn reserve(&mut self, additional: usize) { + self.raw.reserve(additional) + } -impl Map { - /// Create an empty collection. - #[inline] - pub fn new() -> Map { - Map { - raw: RawMap::with_hasher(Default::default()), - } - } + /// Shrinks the capacity of the collection as much as possible. It will drop + /// down as much as possible while maintaining the internal rules + /// and possibly leaving some space in accordance with the resize policy. + #[inline] + pub fn shrink_to_fit(&mut self) { + self.raw.shrink_to_fit() + } - /// Creates an empty collection with the given initial capacity. - #[inline] - pub fn with_capacity(capacity: usize) -> Map { - Map { - raw: RawMap::with_capacity_and_hasher(capacity, Default::default()), - } - } + // Additional stable methods (as of 1.60.0-nightly) that could be added: + // try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> (1.57.0) + // shrink_to(&mut self, min_capacity: usize) (1.56.0) - /// Returns the number of elements the collection can hold without reallocating. - #[inline] - pub fn capacity(&self) -> usize { - self.raw.capacity() - } + /// Returns the number of items in the collection. + #[inline] + pub fn len(&self) -> usize { + self.raw.len() + } - /// Reserves capacity for at least `additional` more elements to be inserted - /// in the collection. The collection may reserve more space to avoid - /// frequent reallocations. - /// - /// # Panics - /// - /// Panics if the new allocation size overflows `usize`. - #[inline] - pub fn reserve(&mut self, additional: usize) { - self.raw.reserve(additional) - } + /// Returns true if there are no items in the collection. + #[inline] + pub fn is_empty(&self) -> bool { + self.raw.is_empty() + } - /// Shrinks the capacity of the collection as much as possible. It will drop - /// down as much as possible while maintaining the internal rules - /// and possibly leaving some space in accordance with the resize policy. - #[inline] - pub fn shrink_to_fit(&mut self) { - self.raw.shrink_to_fit() - } + /// Removes all items from the collection. Keeps the allocated memory for reuse. + #[inline] + pub fn clear(&mut self) { + self.raw.clear() + } - // Additional stable methods (as of 1.60.0-nightly) that could be added: - // try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> (1.57.0) - // shrink_to(&mut self, min_capacity: usize) (1.56.0) + /// Returns a reference to the value stored in the collection for the type `T`, + /// if it exists. + #[inline] + pub fn get>(&self) -> Option<&T> { + self.raw.get(&TypeId::of::()) + .map(|any| unsafe { any.downcast_ref_unchecked::() }) + } - /// Returns the number of items in the collection. - #[inline] - pub fn len(&self) -> usize { - self.raw.len() - } + /// Returns a mutable reference to the value stored in the collection for the type `T`, + /// if it exists. + #[inline] + pub fn get_mut>(&mut self) -> Option<&mut T> { + self.raw.get_mut(&TypeId::of::()) + .map(|any| unsafe { any.downcast_mut_unchecked::() }) + } - /// Returns true if there are no items in the collection. - #[inline] - pub fn is_empty(&self) -> bool { - self.raw.is_empty() - } + /// Sets the value stored in the collection for the type `T`. + /// If the collection already had a value of type `T`, that value is returned. + /// Otherwise, `None` is returned. + #[inline] + pub fn insert>(&mut self, value: T) -> Option { + self.raw.insert(TypeId::of::(), value.into_box()) + .map(|any| unsafe { *any.downcast_unchecked::() }) + } - /// Removes all items from the collection. Keeps the allocated memory for reuse. - #[inline] - pub fn clear(&mut self) { - self.raw.clear() - } + // rustc 1.60.0-nightly has another method try_insert that would be nice when stable. - /// Returns a reference to the value stored in the collection for the type `T`, if it exists. - #[inline] - pub fn get>(&self) -> Option<&T> { - self.raw.get(&TypeId::of::()) - .map(|any| unsafe { any.downcast_ref_unchecked::() }) - } + /// Removes the `T` value from the collection, + /// returning it if there was one or `None` if there was not. + #[inline] + pub fn remove>(&mut self) -> Option { + self.raw.remove(&TypeId::of::()) + .map(|any| *unsafe { any.downcast_unchecked::() }) + } - /// Returns a mutable reference to the value stored in the collection for the type `T`, - /// if it exists. - #[inline] - pub fn get_mut>(&mut self) -> Option<&mut T> { - self.raw.get_mut(&TypeId::of::()) - .map(|any| unsafe { any.downcast_mut_unchecked::() }) - } + /// Returns true if the collection contains a value of type `T`. + #[inline] + pub fn contains>(&self) -> bool { + self.raw.contains_key(&TypeId::of::()) + } - /// Sets the value stored in the collection for the type `T`. - /// If the collection already had a value of type `T`, that value is returned. - /// Otherwise, `None` is returned. - #[inline] - pub fn insert>(&mut self, value: T) -> Option { - self.raw.insert(TypeId::of::(), value.into_box()) - .map(|any| unsafe { *any.downcast_unchecked::() }) - } + /// Gets the entry for the given type in the collection for in-place manipulation + #[inline] + pub fn entry>(&mut self) -> Entry { + match self.raw.entry(TypeId::of::()) { + hash_map::Entry::Occupied(e) => Entry::Occupied(OccupiedEntry { + inner: e, + type_: PhantomData, + }), + hash_map::Entry::Vacant(e) => Entry::Vacant(VacantEntry { + inner: e, + type_: PhantomData, + }), + } + } - // rustc 1.60.0-nightly has another method try_insert that would be nice to add when stable. + /// 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. + #[inline] + pub fn as_raw(&self) -> &RawMap { + &self.raw + } - /// Removes the `T` value from the collection, - /// returning it if there was one or `None` if there was not. - #[inline] - pub fn remove>(&mut self) -> Option { - self.raw.remove(&TypeId::of::()) - .map(|any| *unsafe { any.downcast_unchecked::() }) - } + /// 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. + /// + /// # 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 { + &mut self.raw + } - /// Returns true if the collection contains a value of type `T`. - #[inline] - pub fn contains>(&self) -> bool { - self.raw.contains_key(&TypeId::of::()) - } + /// 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. + #[inline] + pub fn into_raw(self) -> RawMap { + self.raw + } - /// Gets the entry for the given type in the collection for in-place manipulation - #[inline] - pub fn entry>(&mut self) -> Entry { - match self.raw.entry(TypeId::of::()) { - raw_hash_map::Entry::Occupied(e) => Entry::Occupied(OccupiedEntry { - inner: e, - type_: PhantomData, - }), - raw_hash_map::Entry::Vacant(e) => Entry::Vacant(VacantEntry { - inner: e, - type_: PhantomData, - }), + /// 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` generic in the + /// map. + /// + /// Perhaps this will be most practical as `unsafe { Map::from_raw(iter.collect()) }`, + /// `iter` being an iterator over `(TypeId, Box)` 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. + /// + /// # 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) -> Map { + Self { raw } + } } - } - - /// 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 { - &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 { - &mut self.raw - } + impl Extend> for Map { + #[inline] + fn extend>>(&mut self, iter: T) { + for item in iter { + let _ = self.raw.insert(Downcast::type_id(&*item), item); + } + } + } - /// 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 { - self.raw - } + /// A view into a single occupied location in an `Map`. + pub struct OccupiedEntry<'a, A: ?Sized + Downcast, V: 'a> { + inner: hash_map::OccupiedEntry<'a, TypeId, Box, $($entry_generics)?>, + type_: PhantomData, + } - /// 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` generic in the map. - /// - /// Perhaps this will be most practical as `unsafe { Map::from_raw(iter.collect()) }`, iter - /// being an iterator over `(TypeId, Box)` 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) -> Map { - Self { raw } - } -} + /// A view into a single empty location in an `Map`. + pub struct VacantEntry<'a, A: ?Sized + Downcast, V: 'a> { + inner: hash_map::VacantEntry<'a, TypeId, Box, $($entry_generics)?>, + type_: PhantomData, + } -impl Extend> for Map { - #[inline] - fn extend>>(&mut self, iter: T) { - for item in iter { - let _ = self.raw.insert(Downcast::type_id(&*item), item); + /// A view into a single location in an `Map`, which may be vacant or occupied. + pub enum Entry<'a, A: ?Sized + Downcast, V: 'a> { + /// An occupied Entry + Occupied(OccupiedEntry<'a, A, V>), + /// A vacant Entry + Vacant(VacantEntry<'a, A, V>), } - } -} -/// A view into a single occupied location in an `Map`. -pub struct OccupiedEntry<'a, A: ?Sized + Downcast, V: 'a> { - #[cfg(all(feature = "std", not(feature = "hashbrown")))] - inner: raw_hash_map::OccupiedEntry<'a, TypeId, Box>, - #[cfg(feature = "hashbrown")] - inner: raw_hash_map::OccupiedEntry<'a, TypeId, Box, BuildHasherDefault>, - type_: PhantomData, -} + impl<'a, A: ?Sized + Downcast, V: IntoBox> Entry<'a, A, V> { + /// Ensures a value is in the entry by inserting the default if empty, and returns + /// a mutable reference to the value in the entry. + #[inline] + pub fn or_insert(self, default: V) -> &'a mut V { + match self { + Entry::Occupied(inner) => inner.into_mut(), + Entry::Vacant(inner) => inner.insert(default), + } + } -/// A view into a single empty location in an `Map`. -pub struct VacantEntry<'a, A: ?Sized + Downcast, V: 'a> { - #[cfg(all(feature = "std", not(feature = "hashbrown")))] - inner: raw_hash_map::VacantEntry<'a, TypeId, Box>, - #[cfg(feature = "hashbrown")] - inner: raw_hash_map::VacantEntry<'a, TypeId, Box, BuildHasherDefault>, - type_: PhantomData, -} + /// 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. + #[inline] + pub fn or_insert_with V>(self, default: F) -> &'a mut V { + match self { + Entry::Occupied(inner) => inner.into_mut(), + Entry::Vacant(inner) => inner.insert(default()), + } + } -/// A view into a single location in an `Map`, which may be vacant or occupied. -pub enum Entry<'a, A: ?Sized + Downcast, V: 'a> { - /// An occupied Entry - Occupied(OccupiedEntry<'a, A, V>), - /// A vacant Entry - Vacant(VacantEntry<'a, A, V>), -} + /// Ensures a value is in the entry by inserting the default value if empty, + /// and returns a mutable reference to the value in the entry. + #[inline] + pub fn or_default(self) -> &'a mut V where V: Default { + match self { + Entry::Occupied(inner) => inner.into_mut(), + Entry::Vacant(inner) => inner.insert(Default::default()), + } + } -impl<'a, A: ?Sized + Downcast, V: IntoBox> Entry<'a, A, V> { - /// Ensures a value is in the entry by inserting the default if empty, and returns - /// a mutable reference to the value in the entry. - #[inline] - pub fn or_insert(self, default: V) -> &'a mut V { - match self { - Entry::Occupied(inner) => inner.into_mut(), - Entry::Vacant(inner) => inner.insert(default), - } - } + /// Provides in-place mutable access to an occupied entry before any potential inserts + /// into the map. + #[inline] + pub fn and_modify(self, f: F) -> Self { + match self { + Entry::Occupied(mut inner) => { + f(inner.get_mut()); + Entry::Occupied(inner) + }, + Entry::Vacant(inner) => Entry::Vacant(inner), + } + } - /// 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. - #[inline] - pub fn or_insert_with V>(self, default: F) -> &'a mut V { - match self { - Entry::Occupied(inner) => inner.into_mut(), - Entry::Vacant(inner) => inner.insert(default()), + // 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) } - } - /// Ensures a value is in the entry by inserting the default value if empty, - /// and returns a mutable reference to the value in the entry. - #[inline] - pub fn or_default(self) -> &'a mut V where V: Default { - match self { - Entry::Occupied(inner) => inner.into_mut(), - Entry::Vacant(inner) => inner.insert(Default::default()), + impl<'a, A: ?Sized + Downcast, V: IntoBox> OccupiedEntry<'a, A, V> { + /// Gets a reference to the value in the entry + #[inline] + pub fn get(&self) -> &V { + unsafe { self.inner.get().downcast_ref_unchecked() } + } + + /// Gets a mutable reference to the value in the entry + #[inline] + pub fn get_mut(&mut self) -> &mut V { + unsafe { self.inner.get_mut().downcast_mut_unchecked() } + } + + /// 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 V { + unsafe { self.inner.into_mut().downcast_mut_unchecked() } + } + + /// Sets the value of the entry, and returns the entry's old value + #[inline] + pub fn insert(&mut self, value: V) -> V { + unsafe { *self.inner.insert(value.into_box()).downcast_unchecked() } + } + + /// Takes the value out of the entry, and returns it + #[inline] + pub fn remove(self) -> V { + unsafe { *self.inner.remove().downcast_unchecked() } + } } - } - /// Provides in-place mutable access to an occupied entry before any potential inserts into the - /// map. - #[inline] - pub fn and_modify(self, f: F) -> Self { - match self { - Entry::Occupied(mut inner) => { - f(inner.get_mut()); - Entry::Occupied(inner) - }, - Entry::Vacant(inner) => Entry::Vacant(inner), + impl<'a, A: ?Sized + Downcast, V: IntoBox> VacantEntry<'a, A, V> { + /// Sets the value of the entry with the VacantEntry's key, + /// and returns a mutable reference to it + #[inline] + pub fn insert(self, value: V) -> &'a mut V { + unsafe { self.inner.insert(value.into_box()).downcast_mut_unchecked() } + } } - } - // 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) -} + #[cfg(test)] + mod tests { + use crate::CloneAny; + use super::*; + + #[derive(Clone, Debug, PartialEq)] struct A(i32); + #[derive(Clone, Debug, PartialEq)] struct B(i32); + #[derive(Clone, Debug, PartialEq)] struct C(i32); + #[derive(Clone, Debug, PartialEq)] struct D(i32); + #[derive(Clone, Debug, PartialEq)] struct E(i32); + #[derive(Clone, Debug, PartialEq)] struct F(i32); + #[derive(Clone, Debug, PartialEq)] struct J(i32); + + macro_rules! test_entry { + ($name:ident, $init:ty) => { + #[test] + fn $name() { + let mut map = <$init>::new(); + assert_eq!(map.insert(A(10)), None); + assert_eq!(map.insert(B(20)), None); + assert_eq!(map.insert(C(30)), None); + assert_eq!(map.insert(D(40)), None); + assert_eq!(map.insert(E(50)), None); + assert_eq!(map.insert(F(60)), None); + + // Existing key (insert) + match map.entry::() { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut view) => { + assert_eq!(view.get(), &A(10)); + assert_eq!(view.insert(A(100)), A(10)); + } + } + assert_eq!(map.get::().unwrap(), &A(100)); + assert_eq!(map.len(), 6); + + + // Existing key (update) + match map.entry::() { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(mut view) => { + let v = view.get_mut(); + let new_v = B(v.0 * 10); + *v = new_v; + } + } + assert_eq!(map.get::().unwrap(), &B(200)); + assert_eq!(map.len(), 6); + + + // Existing key (remove) + match map.entry::() { + Entry::Vacant(_) => unreachable!(), + Entry::Occupied(view) => { + assert_eq!(view.remove(), C(30)); + } + } + assert_eq!(map.get::(), None); + assert_eq!(map.len(), 5); + + + // Inexistent key (insert) + match map.entry::() { + Entry::Occupied(_) => unreachable!(), + Entry::Vacant(view) => { + assert_eq!(*view.insert(J(1000)), J(1000)); + } + } + assert_eq!(map.get::().unwrap(), &J(1000)); + assert_eq!(map.len(), 6); + + // Entry.or_insert on existing key + map.entry::().or_insert(B(71)).0 += 1; + assert_eq!(map.get::().unwrap(), &B(201)); + assert_eq!(map.len(), 6); + + // Entry.or_insert on nonexisting key + map.entry::().or_insert(C(300)).0 += 1; + assert_eq!(map.get::().unwrap(), &C(301)); + assert_eq!(map.len(), 7); + } + } + } -impl<'a, A: ?Sized + Downcast, V: IntoBox> OccupiedEntry<'a, A, V> { - /// Gets a reference to the value in the entry - #[inline] - pub fn get(&self) -> &V { - unsafe { self.inner.get().downcast_ref_unchecked() } - } + test_entry!(test_entry_any, AnyMap); + test_entry!(test_entry_cloneany, Map); - /// Gets a mutable reference to the value in the entry - #[inline] - pub fn get_mut(&mut self) -> &mut V { - unsafe { self.inner.get_mut().downcast_mut_unchecked() } - } + #[test] + fn test_default() { + let map: AnyMap = Default::default(); + assert_eq!(map.len(), 0); + } - /// 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 V { - unsafe { self.inner.into_mut().downcast_mut_unchecked() } - } + #[test] + fn test_clone() { + let mut map: Map = Map::new(); + let _ = map.insert(A(1)); + let _ = map.insert(B(2)); + let _ = map.insert(D(3)); + let _ = map.insert(E(4)); + let _ = map.insert(F(5)); + let _ = map.insert(J(6)); + let map2 = map.clone(); + assert_eq!(map2.len(), 6); + assert_eq!(map2.get::(), Some(&A(1))); + assert_eq!(map2.get::(), Some(&B(2))); + assert_eq!(map2.get::(), None); + assert_eq!(map2.get::(), Some(&D(3))); + assert_eq!(map2.get::(), Some(&E(4))); + assert_eq!(map2.get::(), Some(&F(5))); + assert_eq!(map2.get::(), Some(&J(6))); + } - /// Sets the value of the entry, and returns the entry's old value - #[inline] - pub fn insert(&mut self, value: V) -> V { - unsafe { *self.inner.insert(value.into_box()).downcast_unchecked() } - } + #[test] + fn test_varieties() { + fn assert_send() { } + fn assert_sync() { } + fn assert_clone() { } + fn assert_debug() { } + assert_send::>(); + assert_send::>(); + assert_sync::>(); + assert_debug::>(); + assert_debug::>(); + assert_debug::>(); + assert_send::>(); + assert_send::>(); + assert_sync::>(); + assert_clone::>(); + assert_clone::>(); + assert_clone::>(); + assert_debug::>(); + assert_debug::>(); + assert_debug::>(); + } - /// Takes the value out of the entry, and returns it - #[inline] - pub fn remove(self) -> V { - unsafe { *self.inner.remove().downcast_unchecked() } - } + #[test] + 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, Box::new(456), Box::new(true)]); + assert_eq!(map.get(), Some(&456)); + assert_eq!(map.get::(), Some(&true)); + assert!(map.get::>().is_none()); + } + } + }; } -impl<'a, A: ?Sized + Downcast, V: IntoBox> VacantEntry<'a, A, V> { - /// Sets the value of the entry with the VacantEntry's key, - /// and returns a mutable reference to it - #[inline] - pub fn insert(self, value: V) -> &'a mut V { - unsafe { self.inner.insert(value.into_box()).downcast_mut_unchecked() } - } +#[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 + ); } /// A hasher designed to eke a little more speed out, given `TypeId`’s known characteristics. @@ -490,166 +625,22 @@ impl Hasher for TypeIdHasher { fn finish(&self) -> u64 { self.value } } -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Clone, Debug, PartialEq)] struct A(i32); - #[derive(Clone, Debug, PartialEq)] struct B(i32); - #[derive(Clone, Debug, PartialEq)] struct C(i32); - #[derive(Clone, Debug, PartialEq)] struct D(i32); - #[derive(Clone, Debug, PartialEq)] struct E(i32); - #[derive(Clone, Debug, PartialEq)] struct F(i32); - #[derive(Clone, Debug, PartialEq)] struct J(i32); - - macro_rules! test_entry { - ($name:ident, $init:ty) => { - #[test] - fn $name() { - let mut map = <$init>::new(); - assert_eq!(map.insert(A(10)), None); - assert_eq!(map.insert(B(20)), None); - assert_eq!(map.insert(C(30)), None); - assert_eq!(map.insert(D(40)), None); - assert_eq!(map.insert(E(50)), None); - assert_eq!(map.insert(F(60)), None); - - // Existing key (insert) - match map.entry::() { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut view) => { - assert_eq!(view.get(), &A(10)); - assert_eq!(view.insert(A(100)), A(10)); - } - } - assert_eq!(map.get::().unwrap(), &A(100)); - assert_eq!(map.len(), 6); - - - // Existing key (update) - match map.entry::() { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(mut view) => { - let v = view.get_mut(); - let new_v = B(v.0 * 10); - *v = new_v; - } - } - assert_eq!(map.get::().unwrap(), &B(200)); - assert_eq!(map.len(), 6); - - - // Existing key (remove) - match map.entry::() { - Entry::Vacant(_) => unreachable!(), - Entry::Occupied(view) => { - assert_eq!(view.remove(), C(30)); - } - } - assert_eq!(map.get::(), None); - assert_eq!(map.len(), 5); - - - // Inexistent key (insert) - match map.entry::() { - Entry::Occupied(_) => unreachable!(), - Entry::Vacant(view) => { - assert_eq!(*view.insert(J(1000)), J(1000)); - } - } - assert_eq!(map.get::().unwrap(), &J(1000)); - assert_eq!(map.len(), 6); - - // Entry.or_insert on existing key - map.entry::().or_insert(B(71)).0 += 1; - assert_eq!(map.get::().unwrap(), &B(201)); - assert_eq!(map.len(), 6); - - // Entry.or_insert on nonexisting key - map.entry::().or_insert(C(300)).0 += 1; - assert_eq!(map.get::().unwrap(), &C(301)); - assert_eq!(map.len(), 7); - } - } - } - - test_entry!(test_entry_any, AnyMap); - test_entry!(test_entry_cloneany, Map); - - #[test] - fn test_default() { - let map: AnyMap = Default::default(); - assert_eq!(map.len(), 0); - } - - #[test] - fn test_clone() { - let mut map: Map = Map::new(); - let _ = map.insert(A(1)); - let _ = map.insert(B(2)); - let _ = map.insert(D(3)); - let _ = map.insert(E(4)); - let _ = map.insert(F(5)); - let _ = map.insert(J(6)); - let map2 = map.clone(); - assert_eq!(map2.len(), 6); - assert_eq!(map2.get::(), Some(&A(1))); - assert_eq!(map2.get::(), Some(&B(2))); - assert_eq!(map2.get::(), None); - assert_eq!(map2.get::(), Some(&D(3))); - assert_eq!(map2.get::(), Some(&E(4))); - assert_eq!(map2.get::(), Some(&F(5))); - assert_eq!(map2.get::(), Some(&J(6))); - } - - #[test] - fn test_varieties() { - fn assert_send() { } - fn assert_sync() { } - fn assert_clone() { } - fn assert_debug() { } - assert_send::>(); - assert_send::>(); - assert_sync::>(); - assert_debug::>(); - assert_debug::>(); - assert_debug::>(); - assert_send::>(); - assert_send::>(); - assert_sync::>(); - assert_clone::>(); - assert_clone::>(); - assert_clone::>(); - assert_debug::>(); - assert_debug::>(); - assert_debug::>(); - } - - #[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::(type_id) }); - } - // Pick a variety of types, just to demonstrate it’s all sane. Normal, zero-sized, unsized, &c. - verify_hashing_with(TypeId::of::()); - verify_hashing_with(TypeId::of::<()>()); - verify_hashing_with(TypeId::of::()); - verify_hashing_with(TypeId::of::<&str>()); - verify_hashing_with(TypeId::of::>()); - } - - #[test] - fn test_extend() { - let mut map = AnyMap::new(); - map.extend([Box::new(123) as Box, Box::new(456), Box::new(true)]); - assert_eq!(map.get(), Some(&456)); - assert_eq!(map.get::(), Some(&true)); - assert!(map.get::>().is_none()); - } +#[test] +fn type_id_hasher() { + #[cfg(not(feature = "std"))] + use alloc::vec::Vec; + use core::hash::Hash; + use core::any::TypeId; + 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::(type_id) }); + } + // Pick a variety of types, just to demonstrate it’s all sane. Normal, zero-sized, unsized, &c. + verify_hashing_with(TypeId::of::()); + verify_hashing_with(TypeId::of::<()>()); + verify_hashing_with(TypeId::of::()); + verify_hashing_with(TypeId::of::<&str>()); + verify_hashing_with(TypeId::of::>()); } diff --git a/test b/test index f635904..49f9775 100755 --- a/test +++ b/test @@ -4,11 +4,10 @@ export RUSTFLAGS="-D warnings" export RUSTDOCFLAGS="-D warnings" run_tests() { 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 --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 --all-features done }