├── .gitignore ├── CHANGELOG.md ├── COPYING ├── Cargo.toml ├── README.md ├── benches └── bench.rs ├── src ├── any.rs └── lib.rs ├── test └── test-oldest-Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 (unreleased) 2 | 3 | Planned once the dust of 1.0.0-beta.1 settles, since 1.0.0-beta.1 ended up 4 | being bigger than I’d earlier intended. 5 | 6 | # 1.0.0-beta.2 (unreleased) 7 | 8 | - Fixed the broken `Extend` implementation added in 1.0.0-beta.1. 9 | 10 | - Split the hashbrown implementation into a new module, `hashbrown`: 11 | std and hashbrown can now coexist completely peacefully, 12 | with `anymap::Map` being powered by `std::collections::hash_map`, 13 | and `anymap::hashbrown::Map` being powered by `hashbrown::hash_map`. 14 | The `raw_hash_map` alias, provided in 1.0.0-beta.1 because of the ambiguity 15 | of what backed `anymap::Map`, is removed as superfluous and useless. 16 | `RawMap` remains, despite not being *required*, as an ergonomic improvement. 17 | With this, we’re back to proper completely additive Cargo features. 18 | 19 | # 1.0.0-beta.1 (2022-01-25) 20 | 21 | - Removed `anymap::any::Any` in favour of just plain `core::any::Any`, since its 22 | `Send`/`Sync` story is now long stable. 23 | 24 | - This loses `Any + Sync`. `CloneAny + Sync` is also removed for consistency. 25 | (So `Any + Sync` is gone, but `Any`, `Any + Send` and `Any + Send + Sync` 26 | remain, plus the same set for `CloneAny`.) 27 | 28 | - `anymap::any::CloneAny` moved to `anymap::CloneAny`. 29 | With nothing public left in `anymap::any`, it is removed. 30 | 31 | - Relicensed from MIT/Apache-2.0 to BlueOak-1.0.0/MIT/Apache-2.0. 32 | 33 | - Increased the minimum supported version of Rust from 1.7.0 to 1.36.0. 34 | 35 | - no_std is now possible in the usual way (default Cargo feature 'std'), 36 | depending on alloc and hashbrown. 37 | 38 | - Removed the `bench` Cargo feature which was mostly to work around historical 39 | Cargo limitations, but was solved by moving benchmarks from `src/lib.rs` to 40 | `benches/bench.rs` even before those limitations were lifted. The benchmarks 41 | still won’t run on anything but nightly, but that don’t signify. 42 | 43 | - Implemented `Default` on `Map` (not just on `RawMap`). 44 | 45 | - Added `Entry::{or_default, and_modify}` (std::collections::hash_map parity). 46 | 47 | - Removed the `anymap::raw` wrapper layer around `std::collections::hash_map`, 48 | in favour of exposing the raw `HashMap` directly. I think there was a reason 49 | I did it that seven years ago, but I think that reason may have dissolved by 50 | now, and I can’t think of it and I don’t like the particular safe 51 | `as_mut`/unsafe insert approach that I used. Because of the hashbrown stuff, 52 | I have retained `anymap::RawMap` is an alias, and `anymap::raw_hash_map` too. 53 | The end result of this is that raw access can finally access things that have 54 | stabilised since Rust 1.7.0, and we’ll no longer need to play catch-up. 55 | 56 | - Worked around the spurious `where_clauses_object_safety` future-compatibility lint that has been raised since mid-2018. 57 | If you put `#![allow(where_clauses_object_safety)]` on your binary crates for this reason, you can remove it. 58 | 59 | # 0.12.1 (2017-01-20) 60 | 61 | - Remove superfluous Clone bound on Entry methods (#26) 62 | - Consistent application of `#[inline]` where it should be 63 | - Fix bad performance (see 724f94758def9f71ad27ff49e47e908a431c2728 for details) 64 | 65 | # 0.12.0 (2016-03-05) 66 | 67 | - Ungate `drain` iterator (stable from Rust 1.6.0) 68 | - Ungate efficient hashing (stable from Rust 1.7.0) 69 | - Remove `unstable` Cargo feature (in favour of a `bench` feature for benchmarking) 70 | 71 | # 0.11.2 (2016-01-22) 72 | 73 | - Rust warning updates only 74 | 75 | # 0.11.1 (2015-06-24) 76 | 77 | - Unstable Rust compatibility updates 78 | 79 | # 0.11.0 (2015-06-10) 80 | 81 | - Support concurrent maps (`Send + Sync` bound) 82 | - Rename `nightly` feature to `unstable` 83 | - Implement `Debug` for `Map` and `RawMap` 84 | - Replace `clone` Cargo feature with arcane DST magicks 85 | 86 | # Older releases (from the initial code on 2014-06-12 to 0.10.3 on 2015-04-18) 87 | 88 | I’m not giving a changelog for these artefacts of ancient history. 89 | If you really care you can look through the Git history easily enough. 90 | Most of the releases were just compensating for changes to the language 91 | (that being before Rust 1.0; yes, this crate has been around for a while). 92 | 93 | I do think that [`src/lib.rs` in the first commit] is a work of art, 94 | a thing of great beauty worth looking at; its simplicity is delightful, 95 | and it doesn’t even need to contain any unsafe code. 96 | 97 | [`src/lib.rs` in the first commit]: https://github.com/chris-morgan/anymap/tree/a294948f57dee47bb284d6a3ae1b8f61a902a03c/src/lib.rs 98 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright © 2014–2022 Chris Morgan 2 | 3 | This project is distributed under the terms of three different licenses, 4 | at your choice: 5 | 6 | - Blue Oak Model License 1.0.0: https://blueoakcouncil.org/license/1.0.0 7 | - MIT License: https://opensource.org/licenses/MIT 8 | - Apache License, Version 2.0: https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | If you do not have particular cause to select the MIT or the Apache-2.0 11 | license, Chris Morgan recommends that you select BlueOak-1.0.0, which is 12 | better and simpler than both MIT and Apache-2.0, which are only offered 13 | due to their greater recognition and their conventional use in the Rust 14 | ecosystem. (BlueOak-1.0.0 was only published in March 2019.) 15 | 16 | When using this code, ensure you comply with the terms of at least one of 17 | these licenses. 18 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anymap" 3 | version = "1.0.0-beta.2" 4 | authors = ["Chris Morgan "] 5 | edition = "2018" 6 | rust-version = "1.36" 7 | description = "A safe and convenient store for one value of each type" 8 | repository = "https://github.com/chris-morgan/anymap" 9 | keywords = ["container", "any", "map"] 10 | categories = ["rust-patterns", "data-structures", "no-std"] 11 | license = "BlueOak-1.0.0 OR MIT OR Apache-2.0" 12 | include = ["/README.md", "/COPYING", "/CHANGELOG.md", "/src"] 13 | 14 | [package.metadata.docs.rs] 15 | all-features = true 16 | 17 | [features] 18 | default = ["std"] 19 | std = [] 20 | 21 | [dependencies] 22 | # The hashbrown feature, disabled by default, is exposed under different stability guarantees than the usual SemVer ones: by preference the version range will only be extended, but it may be shrunk in a MINOR release. See README.md. 23 | hashbrown = { version = ">=0.1.1, <0.13", optional = true } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ``AnyMap``, a safe and convenient store for one value of each type 2 | 3 | ``AnyMap`` is a type-safe wrapper around ``HashMap>`` that lets you not worry about ``TypeId`` or downcasting, but just get on with storing one each of a bag of diverse types, which is really useful for extensibility in some sorts of libraries. 4 | 5 | ## Background 6 | 7 | If you’re familiar with Go and Go web frameworks, you may have come across the common “environment” pattern for storing data related to the request. It’s typically something like ``map[string]interface{}`` and is accessed with arbitrary strings which may clash and type assertions which are a little unwieldy and must be used very carefully. (Personally I would consider that it is just *asking* for things to blow up in your face.) In a language like Go, lacking in generics, this is the best that can be done; such a thing cannot possibly be made safe without generics. 8 | 9 | As another example of such an interface, JavaScript objects are exactly the same—a mapping of string keys to arbitrary values. (There it is actually *more* dangerous, because methods and fields/attributes/properties are on the same plane—though it’s *possible* to use `Map` these days.) 10 | 11 | Fortunately, we can do better than these things in Rust. Our type system is quite equal to easy, robust expression of such problems. 12 | 13 | ## Example 14 | 15 | ```rust 16 | let mut data = anymap::AnyMap::new(); 17 | assert_eq!(data.get(), None::<&i32>); 18 | data.insert(42i32); 19 | assert_eq!(data.get(), Some(&42i32)); 20 | data.remove::(); 21 | assert_eq!(data.get::(), None); 22 | 23 | #[derive(Clone, PartialEq, Debug)] 24 | struct Foo { 25 | str: String, 26 | } 27 | 28 | assert_eq!(data.get::(), None); 29 | data.insert(Foo { str: format!("foo") }); 30 | assert_eq!(data.get(), Some(&Foo { str: format!("foo") })); 31 | data.get_mut::().map(|foo| foo.str.push('t')); 32 | assert_eq!(&*data.get::().unwrap().str, "foot"); 33 | ``` 34 | 35 | ## Features 36 | 37 | - Store up to one value for each type in a bag. 38 | - Add `Send` or `Send + Sync` bounds. 39 | - You can opt into making the map `Clone`. (In theory you could add all kinds of other functionality, but you can’t readily make this work *generically*, and the bones of it are simple enough that it becomes better to make your own extension of `Any` and reimplement `AnyMap`.) 40 | - no_std if you like. 41 | 42 | ## Cargo features/dependencies/usage 43 | 44 | Typical Cargo.toml usage, providing `anymap::AnyMap` *et al.* backed by `std::collections::HashMap`: 45 | 46 | ```toml 47 | [dependencies] 48 | anymap = "1.0.0-beta.2" 49 | ``` 50 | 51 | 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: 52 | 53 | ```toml 54 | [dependencies] 55 | anymap = { version = "1.0.0-beta.2", default-features = false, features = ["hashbrown"] } 56 | ``` 57 | 58 | **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`). 59 | 60 | ## Unsafe code in this library 61 | 62 | This library uses a fair bit of unsafe code for several reasons: 63 | 64 | - To support `CloneAny`, unsafe code is currently required (because the downcast methods are defined on `dyn Any` rather than being trait methods, and upcasting is an incomplete feature in rustc); if you wanted to ditch `Clone` support this unsafety could be removed. 65 | 66 | - For `dyn CloneAny + Send` and `dyn CloneAny + Send + Sync`’s `Clone` implementation, an unsafe block is used to attach the auto traits where safe code used to be used, in order to avoid a [spurious future-compatibility lint](https://github.com/rust-lang/rust/issues/51443#issuecomment-421988013). 67 | 68 | - In the interests of performance, type ID checks are skipped as unnecessary because of the invariants of the data structure (though this does come at the cost of `Map::{as_raw_mut, into_raw}` being marked unsafe). 69 | 70 | It is possible to remove all unsafe code at the cost of only `CloneAny` functionality and a little performance. The `safe` branch in the Git repository contains a couple of commits demonstrating the concept. It’s quite straightforward; the core of this library is very simple and perfectly safe. 71 | 72 | ## Colophon 73 | 74 | **Authorship:** [Chris Morgan](https://chrismorgan.info/) is the author and maintainer of this library. 75 | 76 | **Licensing:** this library is distributed under the terms of the 77 | [Blue Oak Model License 1.0.0](https://blueoakcouncil.org/license/1.0.0), the 78 | [MIT License](https://opensource.org/licenses/MIT) and the 79 | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0), at your choice. 80 | See [COPYING](COPYING) for details. 81 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate anymap; 4 | 5 | extern crate test; 6 | 7 | use anymap::AnyMap; 8 | 9 | use test::Bencher; 10 | use test::black_box; 11 | 12 | #[bench] 13 | fn insertion(b: &mut Bencher) { 14 | b.iter(|| { 15 | let mut data = AnyMap::new(); 16 | for _ in 0..100 { 17 | let _ = data.insert(42); 18 | } 19 | }) 20 | } 21 | 22 | #[bench] 23 | fn get_missing(b: &mut Bencher) { 24 | b.iter(|| { 25 | let data = AnyMap::new(); 26 | for _ in 0..100 { 27 | assert_eq!(data.get(), None::<&i32>); 28 | } 29 | }) 30 | } 31 | 32 | #[bench] 33 | fn get_present(b: &mut Bencher) { 34 | b.iter(|| { 35 | let mut data = AnyMap::new(); 36 | let _ = data.insert(42); 37 | // These inner loops are a feeble attempt to drown the other factors. 38 | for _ in 0..100 { 39 | assert_eq!(data.get(), Some(&42)); 40 | } 41 | }) 42 | } 43 | 44 | macro_rules! big_benchmarks { 45 | ($name:ident, $($T:ident)*) => ( 46 | #[bench] 47 | fn $name(b: &mut Bencher) { 48 | $( 49 | struct $T(&'static str); 50 | )* 51 | 52 | b.iter(|| { 53 | let mut data = AnyMap::new(); 54 | $( 55 | let _ = black_box(data.insert($T(stringify!($T)))); 56 | )* 57 | $( 58 | let _ = black_box(data.get::<$T>()); 59 | )* 60 | }) 61 | } 62 | ); 63 | } 64 | 65 | // Caution: if the macro does too much (e.g. assertions) this goes from being slow to being 66 | // *really* slow (like add a minute for each assertion on it) and memory-hungry (like, adding 67 | // several hundred megabytes to the peak for each assertion). 68 | big_benchmarks! { 69 | insert_and_get_on_260_types, 70 | A0 B0 C0 D0 E0 F0 G0 H0 I0 J0 K0 L0 M0 N0 O0 P0 Q0 R0 S0 T0 U0 V0 W0 X0 Y0 Z0 71 | A1 B1 C1 D1 E1 F1 G1 H1 I1 J1 K1 L1 M1 N1 O1 P1 Q1 R1 S1 T1 U1 V1 W1 X1 Y1 Z1 72 | A2 B2 C2 D2 E2 F2 G2 H2 I2 J2 K2 L2 M2 N2 O2 P2 Q2 R2 S2 T2 U2 V2 W2 X2 Y2 Z2 73 | A3 B3 C3 D3 E3 F3 G3 H3 I3 J3 K3 L3 M3 N3 O3 P3 Q3 R3 S3 T3 U3 V3 W3 X3 Y3 Z3 74 | A4 B4 C4 D4 E4 F4 G4 H4 I4 J4 K4 L4 M4 N4 O4 P4 Q4 R4 S4 T4 U4 V4 W4 X4 Y4 Z4 75 | A5 B5 C5 D5 E5 F5 G5 H5 I5 J5 K5 L5 M5 N5 O5 P5 Q5 R5 S5 T5 U5 V5 W5 X5 Y5 Z5 76 | A6 B6 C6 D6 E6 F6 G6 H6 I6 J6 K6 L6 M6 N6 O6 P6 Q6 R6 S6 T6 U6 V6 W6 X6 Y6 Z6 77 | A7 B7 C7 D7 E7 F7 G7 H7 I7 J7 K7 L7 M7 N7 O7 P7 Q7 R7 S7 T7 U7 V7 W7 X7 Y7 Z7 78 | A8 B8 C8 D8 E8 F8 G8 H8 I8 J8 K8 L8 M8 N8 O8 P8 Q8 R8 S8 T8 U8 V8 W8 X8 Y8 Z8 79 | A9 B9 C9 D9 E9 F9 G9 H9 I9 J9 K9 L9 M9 N9 O9 P9 Q9 R9 S9 T9 U9 V9 W9 X9 Y9 Z9 80 | } 81 | 82 | big_benchmarks! { 83 | insert_and_get_on_26_types, 84 | A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 85 | } 86 | -------------------------------------------------------------------------------- /src/any.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use core::any::{Any, TypeId}; 3 | #[cfg(not(feature = "std"))] 4 | use alloc::boxed::Box; 5 | 6 | #[doc(hidden)] 7 | pub trait CloneToAny { 8 | /// Clone `self` into a new `Box` object. 9 | fn clone_to_any(&self) -> Box; 10 | } 11 | 12 | impl CloneToAny for T { 13 | #[inline] 14 | fn clone_to_any(&self) -> Box { 15 | Box::new(self.clone()) 16 | } 17 | } 18 | 19 | macro_rules! impl_clone { 20 | ($t:ty) => { 21 | impl Clone for Box<$t> { 22 | #[inline] 23 | fn clone(&self) -> Box<$t> { 24 | // SAFETY: this dance is to reapply any Send/Sync marker. I’m not happy about this 25 | // approach, given that I used to do it in safe code, but then came a dodgy 26 | // future-compatibility warning where_clauses_object_safety, which is spurious for 27 | // auto traits but still super annoying (future-compatibility lints seem to mean 28 | // your bin crate needs a corresponding allow!). Although I explained my plight¹ 29 | // and it was all explained and agreed upon, no action has been taken. So I finally 30 | // caved and worked around it by doing it this way, which matches what’s done for 31 | // core::any², so it’s probably not *too* bad. 32 | // 33 | // ¹ https://github.com/rust-lang/rust/issues/51443#issuecomment-421988013 34 | // ² https://github.com/rust-lang/rust/blob/e7825f2b690c9a0d21b6f6d84c404bb53b151b38/library/alloc/src/boxed.rs#L1613-L1616 35 | let clone: Box = (**self).clone_to_any(); 36 | let raw: *mut dyn CloneAny = Box::into_raw(clone); 37 | unsafe { Box::from_raw(raw as *mut $t) } 38 | } 39 | } 40 | 41 | impl fmt::Debug for $t { 42 | #[inline] 43 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 44 | f.pad(stringify!($t)) 45 | } 46 | } 47 | } 48 | } 49 | 50 | /// Methods for downcasting from an `Any`-like trait object. 51 | /// 52 | /// This should only be implemented on trait objects for subtraits of `Any`, though you can 53 | /// implement it for other types and it’ll work fine, so long as your implementation is correct. 54 | pub trait Downcast { 55 | /// Gets the `TypeId` of `self`. 56 | fn type_id(&self) -> TypeId; 57 | 58 | // Note the bound through these downcast methods is 'static, rather than the inexpressible 59 | // concept of Self-but-as-a-trait (where Self is `dyn Trait`). This is sufficient, exceeding 60 | // TypeId’s requirements. Sure, you *can* do CloneAny.downcast_unchecked::() and the 61 | // type system won’t protect you, but that doesn’t introduce any unsafety: the method is 62 | // already unsafe because you can specify the wrong type, and if this were exposing safe 63 | // downcasting, CloneAny.downcast::() would just return an error, which is just as 64 | // correct. 65 | // 66 | // Now in theory we could also add T: ?Sized, but that doesn’t play nicely with the common 67 | // implementation, so I’m doing without it. 68 | 69 | /// Downcast from `&Any` to `&T`, without checking the type matches. 70 | /// 71 | /// # Safety 72 | /// 73 | /// The caller must ensure that `T` matches the trait object, on pain of *undefined behaviour*. 74 | unsafe fn downcast_ref_unchecked(&self) -> &T; 75 | 76 | /// Downcast from `&mut Any` to `&mut T`, without checking the type matches. 77 | /// 78 | /// # Safety 79 | /// 80 | /// The caller must ensure that `T` matches the trait object, on pain of *undefined behaviour*. 81 | unsafe fn downcast_mut_unchecked(&mut self) -> &mut T; 82 | 83 | /// Downcast from `Box` to `Box`, without checking the type matches. 84 | /// 85 | /// # Safety 86 | /// 87 | /// The caller must ensure that `T` matches the trait object, on pain of *undefined behaviour*. 88 | unsafe fn downcast_unchecked(self: Box) -> Box; 89 | } 90 | 91 | /// A trait for the conversion of an object into a boxed trait object. 92 | pub trait IntoBox: Any { 93 | /// Convert self into the appropriate boxed form. 94 | fn into_box(self) -> Box; 95 | } 96 | 97 | macro_rules! implement { 98 | ($any_trait:ident $(+ $auto_traits:ident)*) => { 99 | impl Downcast for dyn $any_trait $(+ $auto_traits)* { 100 | #[inline] 101 | fn type_id(&self) -> TypeId { 102 | self.type_id() 103 | } 104 | 105 | #[inline] 106 | unsafe fn downcast_ref_unchecked(&self) -> &T { 107 | &*(self as *const Self as *const T) 108 | } 109 | 110 | #[inline] 111 | unsafe fn downcast_mut_unchecked(&mut self) -> &mut T { 112 | &mut *(self as *mut Self as *mut T) 113 | } 114 | 115 | #[inline] 116 | unsafe fn downcast_unchecked(self: Box) -> Box { 117 | Box::from_raw(Box::into_raw(self) as *mut T) 118 | } 119 | } 120 | 121 | impl IntoBox for T { 122 | #[inline] 123 | fn into_box(self) -> Box { 124 | Box::new(self) 125 | } 126 | } 127 | } 128 | } 129 | 130 | implement!(Any); 131 | implement!(Any + Send); 132 | implement!(Any + Send + Sync); 133 | 134 | /// [`Any`], but with cloning. 135 | /// 136 | /// Every type with no non-`'static` references that implements `Clone` implements `CloneAny`. 137 | /// See [`core::any`] for more details on `Any` in general. 138 | pub trait CloneAny: Any + CloneToAny { } 139 | impl CloneAny for T { } 140 | implement!(CloneAny); 141 | implement!(CloneAny + Send); 142 | implement!(CloneAny + Send + Sync); 143 | impl_clone!(dyn CloneAny); 144 | impl_clone!(dyn CloneAny + Send); 145 | impl_clone!(dyn CloneAny + Send + Sync); 146 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides a safe and convenient store for one value of each type. 2 | //! 3 | //! Your starting point is [`Map`]. It has an example. 4 | //! 5 | //! # Cargo features 6 | //! 7 | //! This crate has two independent features, each of which provides an implementation providing 8 | //! types `Map`, `AnyMap`, `OccupiedEntry`, `VacantEntry`, `Entry` and `RawMap`: 9 | //! 10 | #![cfg_attr(feature = "std", doc = " - **std** (default, *enabled* in this build):")] 11 | #![cfg_attr(not(feature = "std"), doc = " - **std** (default, *disabled* in this build):")] 12 | //! an implementation using `std::collections::hash_map`, placed in the crate root 13 | //! (e.g. `anymap::AnyMap`). 14 | //! 15 | #![cfg_attr(feature = "hashbrown", doc = " - **hashbrown** (optional; *enabled* in this build):")] 16 | #![cfg_attr(not(feature = "hashbrown"), doc = " - **hashbrown** (optional; *disabled* in this build):")] 17 | //! an implementation using `alloc` and `hashbrown::hash_map`, placed in a module `hashbrown` 18 | //! (e.g. `anymap::hashbrown::AnyMap`). 19 | 20 | #![warn(missing_docs, unused_results)] 21 | 22 | #![cfg_attr(not(feature = "std"), no_std)] 23 | 24 | use core::convert::TryInto; 25 | use core::hash::Hasher; 26 | 27 | #[cfg(not(feature = "std"))] 28 | extern crate alloc; 29 | 30 | pub use crate::any::CloneAny; 31 | 32 | mod any; 33 | 34 | #[cfg(any(feature = "std", feature = "hashbrown"))] 35 | macro_rules! everything { 36 | ($example_init:literal, $($parent:ident)::+ $(, $entry_generics:ty)?) => { 37 | use core::any::{Any, TypeId}; 38 | use core::hash::BuildHasherDefault; 39 | use core::marker::PhantomData; 40 | 41 | #[cfg(not(feature = "std"))] 42 | use alloc::boxed::Box; 43 | 44 | use ::$($parent)::+::hash_map::{self, HashMap}; 45 | 46 | use crate::any::{Downcast, IntoBox}; 47 | 48 | /// Raw access to the underlying `HashMap`. 49 | /// 50 | /// This alias is provided for convenience because of the ugly third generic parameter. 51 | pub type RawMap = HashMap, BuildHasherDefault>; 52 | 53 | /// A collection containing zero or one values for any given type and allowing convenient, 54 | /// type-safe access to those values. 55 | /// 56 | /// The type parameter `A` allows you to use a different value type; normally you will want 57 | /// it to be `core::any::Any` (also known as `std::any::Any`), but there are other choices: 58 | /// 59 | /// - If you want the entire map to be cloneable, use `CloneAny` instead of `Any`; with 60 | /// that, you can only add types that implement `Clone` to the map. 61 | /// - You can add on `+ Send` or `+ Send + Sync` (e.g. `Map`) to add those 62 | /// auto traits. 63 | /// 64 | /// Cumulatively, there are thus six forms of map: 65 | /// 66 | /// - [Map]<dyn [core::any::Any]>, 67 | /// also spelled [`AnyMap`] for convenience. 68 | /// - [Map]<dyn [core::any::Any] + Send> 69 | /// - [Map]<dyn [core::any::Any] + Send + Sync> 70 | /// - [Map]<dyn [CloneAny]> 71 | /// - [Map]<dyn [CloneAny] + Send> 72 | /// - [Map]<dyn [CloneAny] + Send + Sync> 73 | /// 74 | /// ## Example 75 | /// 76 | /// (Here using the [`AnyMap`] convenience alias; the first line could use 77 | /// [anymap::Map][Map]::<[core::any::Any]>::new() instead if desired.) 78 | /// 79 | /// ```rust 80 | #[doc = $example_init] 81 | /// assert_eq!(data.get(), None::<&i32>); 82 | /// data.insert(42i32); 83 | /// assert_eq!(data.get(), Some(&42i32)); 84 | /// data.remove::(); 85 | /// assert_eq!(data.get::(), None); 86 | /// 87 | /// #[derive(Clone, PartialEq, Debug)] 88 | /// struct Foo { 89 | /// str: String, 90 | /// } 91 | /// 92 | /// assert_eq!(data.get::(), None); 93 | /// data.insert(Foo { str: format!("foo") }); 94 | /// assert_eq!(data.get(), Some(&Foo { str: format!("foo") })); 95 | /// data.get_mut::().map(|foo| foo.str.push('t')); 96 | /// assert_eq!(&*data.get::().unwrap().str, "foot"); 97 | /// ``` 98 | /// 99 | /// Values containing non-static references are not permitted. 100 | #[derive(Debug)] 101 | pub struct Map { 102 | raw: RawMap, 103 | } 104 | 105 | // #[derive(Clone)] would want A to implement Clone, but in reality only Box can. 106 | impl Clone for Map where Box: Clone { 107 | #[inline] 108 | fn clone(&self) -> Map { 109 | Map { 110 | raw: self.raw.clone(), 111 | } 112 | } 113 | } 114 | 115 | /// The most common type of `Map`: just using `Any`; [Map]<dyn [Any]>. 116 | /// 117 | /// Why is this a separate type alias rather than a default value for `Map`? 118 | /// `Map::new()` doesn’t seem to be happy to infer that it should go with the default 119 | /// value. It’s a bit sad, really. Ah well, I guess this approach will do. 120 | pub type AnyMap = Map; 121 | 122 | impl Default for Map { 123 | #[inline] 124 | fn default() -> Map { 125 | Map::new() 126 | } 127 | } 128 | 129 | impl Map { 130 | /// Create an empty collection. 131 | #[inline] 132 | pub fn new() -> Map { 133 | Map { 134 | raw: RawMap::with_hasher(Default::default()), 135 | } 136 | } 137 | 138 | /// Creates an empty collection with the given initial capacity. 139 | #[inline] 140 | pub fn with_capacity(capacity: usize) -> Map { 141 | Map { 142 | raw: RawMap::with_capacity_and_hasher(capacity, Default::default()), 143 | } 144 | } 145 | 146 | /// Returns the number of elements the collection can hold without reallocating. 147 | #[inline] 148 | pub fn capacity(&self) -> usize { 149 | self.raw.capacity() 150 | } 151 | 152 | /// Reserves capacity for at least `additional` more elements to be inserted 153 | /// in the collection. The collection may reserve more space to avoid 154 | /// frequent reallocations. 155 | /// 156 | /// # Panics 157 | /// 158 | /// Panics if the new allocation size overflows `usize`. 159 | #[inline] 160 | pub fn reserve(&mut self, additional: usize) { 161 | self.raw.reserve(additional) 162 | } 163 | 164 | /// Shrinks the capacity of the collection as much as possible. It will drop 165 | /// down as much as possible while maintaining the internal rules 166 | /// and possibly leaving some space in accordance with the resize policy. 167 | #[inline] 168 | pub fn shrink_to_fit(&mut self) { 169 | self.raw.shrink_to_fit() 170 | } 171 | 172 | // Additional stable methods (as of 1.60.0-nightly) that could be added: 173 | // try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> (1.57.0) 174 | // shrink_to(&mut self, min_capacity: usize) (1.56.0) 175 | 176 | /// Returns the number of items in the collection. 177 | #[inline] 178 | pub fn len(&self) -> usize { 179 | self.raw.len() 180 | } 181 | 182 | /// Returns true if there are no items in the collection. 183 | #[inline] 184 | pub fn is_empty(&self) -> bool { 185 | self.raw.is_empty() 186 | } 187 | 188 | /// Removes all items from the collection. Keeps the allocated memory for reuse. 189 | #[inline] 190 | pub fn clear(&mut self) { 191 | self.raw.clear() 192 | } 193 | 194 | /// Returns a reference to the value stored in the collection for the type `T`, 195 | /// if it exists. 196 | #[inline] 197 | pub fn get>(&self) -> Option<&T> { 198 | self.raw.get(&TypeId::of::()) 199 | .map(|any| unsafe { any.downcast_ref_unchecked::() }) 200 | } 201 | 202 | /// Returns a mutable reference to the value stored in the collection for the type `T`, 203 | /// if it exists. 204 | #[inline] 205 | pub fn get_mut>(&mut self) -> Option<&mut T> { 206 | self.raw.get_mut(&TypeId::of::()) 207 | .map(|any| unsafe { any.downcast_mut_unchecked::() }) 208 | } 209 | 210 | /// Sets the value stored in the collection for the type `T`. 211 | /// If the collection already had a value of type `T`, that value is returned. 212 | /// Otherwise, `None` is returned. 213 | #[inline] 214 | pub fn insert>(&mut self, value: T) -> Option { 215 | self.raw.insert(TypeId::of::(), value.into_box()) 216 | .map(|any| unsafe { *any.downcast_unchecked::() }) 217 | } 218 | 219 | // rustc 1.60.0-nightly has another method try_insert that would be nice when stable. 220 | 221 | /// Removes the `T` value from the collection, 222 | /// returning it if there was one or `None` if there was not. 223 | #[inline] 224 | pub fn remove>(&mut self) -> Option { 225 | self.raw.remove(&TypeId::of::()) 226 | .map(|any| *unsafe { any.downcast_unchecked::() }) 227 | } 228 | 229 | /// Returns true if the collection contains a value of type `T`. 230 | #[inline] 231 | pub fn contains>(&self) -> bool { 232 | self.raw.contains_key(&TypeId::of::()) 233 | } 234 | 235 | /// Gets the entry for the given type in the collection for in-place manipulation 236 | #[inline] 237 | pub fn entry>(&mut self) -> Entry { 238 | match self.raw.entry(TypeId::of::()) { 239 | hash_map::Entry::Occupied(e) => Entry::Occupied(OccupiedEntry { 240 | inner: e, 241 | type_: PhantomData, 242 | }), 243 | hash_map::Entry::Vacant(e) => Entry::Vacant(VacantEntry { 244 | inner: e, 245 | type_: PhantomData, 246 | }), 247 | } 248 | } 249 | 250 | /// Get access to the raw hash map that backs this. 251 | /// 252 | /// This will seldom be useful, but it’s conceivable that you could wish to iterate 253 | /// over all the items in the collection, and this lets you do that. 254 | #[inline] 255 | pub fn as_raw(&self) -> &RawMap { 256 | &self.raw 257 | } 258 | 259 | /// Get mutable access to the raw hash map that backs this. 260 | /// 261 | /// This will seldom be useful, but it’s conceivable that you could wish to iterate 262 | /// over all the items in the collection mutably, or drain or something, or *possibly* 263 | /// even batch insert, and this lets you do that. 264 | /// 265 | /// # Safety 266 | /// 267 | /// If you insert any values to the raw map, the key (a `TypeId`) must match the 268 | /// value’s type, or *undefined behaviour* will occur when you access those values. 269 | /// 270 | /// (*Removing* entries is perfectly safe.) 271 | #[inline] 272 | pub unsafe fn as_raw_mut(&mut self) -> &mut RawMap { 273 | &mut self.raw 274 | } 275 | 276 | /// Convert this into the raw hash map that backs this. 277 | /// 278 | /// This will seldom be useful, but it’s conceivable that you could wish to consume all 279 | /// the items in the collection and do *something* with some or all of them, and this 280 | /// lets you do that, without the `unsafe` that `.as_raw_mut().drain()` would require. 281 | #[inline] 282 | pub fn into_raw(self) -> RawMap { 283 | self.raw 284 | } 285 | 286 | /// Construct a map from a collection of raw values. 287 | /// 288 | /// You know what? I can’t immediately think of any legitimate use for this, especially 289 | /// because of the requirement of the `BuildHasherDefault` generic in the 290 | /// map. 291 | /// 292 | /// Perhaps this will be most practical as `unsafe { Map::from_raw(iter.collect()) }`, 293 | /// `iter` being an iterator over `(TypeId, Box)` pairs. Eh, this method provides 294 | /// symmetry with `into_raw`, so I don’t care if literally no one ever uses it. I’m not 295 | /// even going to write a test for it, it’s so trivial. 296 | /// 297 | /// # Safety 298 | /// 299 | /// For all entries in the raw map, the key (a `TypeId`) must match the value’s type, 300 | /// or *undefined behaviour* will occur when you access that entry. 301 | #[inline] 302 | pub unsafe fn from_raw(raw: RawMap) -> Map { 303 | Self { raw } 304 | } 305 | } 306 | 307 | impl Extend> for Map { 308 | #[inline] 309 | fn extend>>(&mut self, iter: T) { 310 | for item in iter { 311 | let _ = self.raw.insert(Downcast::type_id(&*item), item); 312 | } 313 | } 314 | } 315 | 316 | /// A view into a single occupied location in an `Map`. 317 | pub struct OccupiedEntry<'a, A: ?Sized + Downcast, V: 'a> { 318 | inner: hash_map::OccupiedEntry<'a, TypeId, Box, $($entry_generics)?>, 319 | type_: PhantomData, 320 | } 321 | 322 | /// A view into a single empty location in an `Map`. 323 | pub struct VacantEntry<'a, A: ?Sized + Downcast, V: 'a> { 324 | inner: hash_map::VacantEntry<'a, TypeId, Box, $($entry_generics)?>, 325 | type_: PhantomData, 326 | } 327 | 328 | /// A view into a single location in an `Map`, which may be vacant or occupied. 329 | pub enum Entry<'a, A: ?Sized + Downcast, V: 'a> { 330 | /// An occupied Entry 331 | Occupied(OccupiedEntry<'a, A, V>), 332 | /// A vacant Entry 333 | Vacant(VacantEntry<'a, A, V>), 334 | } 335 | 336 | impl<'a, A: ?Sized + Downcast, V: IntoBox> Entry<'a, A, V> { 337 | /// Ensures a value is in the entry by inserting the default if empty, and returns 338 | /// a mutable reference to the value in the entry. 339 | #[inline] 340 | pub fn or_insert(self, default: V) -> &'a mut V { 341 | match self { 342 | Entry::Occupied(inner) => inner.into_mut(), 343 | Entry::Vacant(inner) => inner.insert(default), 344 | } 345 | } 346 | 347 | /// Ensures a value is in the entry by inserting the result of the default function if 348 | /// empty, and returns a mutable reference to the value in the entry. 349 | #[inline] 350 | pub fn or_insert_with V>(self, default: F) -> &'a mut V { 351 | match self { 352 | Entry::Occupied(inner) => inner.into_mut(), 353 | Entry::Vacant(inner) => inner.insert(default()), 354 | } 355 | } 356 | 357 | /// Ensures a value is in the entry by inserting the default value if empty, 358 | /// and returns a mutable reference to the value in the entry. 359 | #[inline] 360 | pub fn or_default(self) -> &'a mut V where V: Default { 361 | match self { 362 | Entry::Occupied(inner) => inner.into_mut(), 363 | Entry::Vacant(inner) => inner.insert(Default::default()), 364 | } 365 | } 366 | 367 | /// Provides in-place mutable access to an occupied entry before any potential inserts 368 | /// into the map. 369 | #[inline] 370 | pub fn and_modify(self, f: F) -> Self { 371 | match self { 372 | Entry::Occupied(mut inner) => { 373 | f(inner.get_mut()); 374 | Entry::Occupied(inner) 375 | }, 376 | Entry::Vacant(inner) => Entry::Vacant(inner), 377 | } 378 | } 379 | 380 | // Additional stable methods (as of 1.60.0-nightly) that could be added: 381 | // insert_entry(self, value: V) -> OccupiedEntry<'a, K, V> (1.59.0) 382 | } 383 | 384 | impl<'a, A: ?Sized + Downcast, V: IntoBox> OccupiedEntry<'a, A, V> { 385 | /// Gets a reference to the value in the entry 386 | #[inline] 387 | pub fn get(&self) -> &V { 388 | unsafe { self.inner.get().downcast_ref_unchecked() } 389 | } 390 | 391 | /// Gets a mutable reference to the value in the entry 392 | #[inline] 393 | pub fn get_mut(&mut self) -> &mut V { 394 | unsafe { self.inner.get_mut().downcast_mut_unchecked() } 395 | } 396 | 397 | /// Converts the OccupiedEntry into a mutable reference to the value in the entry 398 | /// with a lifetime bound to the collection itself 399 | #[inline] 400 | pub fn into_mut(self) -> &'a mut V { 401 | unsafe { self.inner.into_mut().downcast_mut_unchecked() } 402 | } 403 | 404 | /// Sets the value of the entry, and returns the entry's old value 405 | #[inline] 406 | pub fn insert(&mut self, value: V) -> V { 407 | unsafe { *self.inner.insert(value.into_box()).downcast_unchecked() } 408 | } 409 | 410 | /// Takes the value out of the entry, and returns it 411 | #[inline] 412 | pub fn remove(self) -> V { 413 | unsafe { *self.inner.remove().downcast_unchecked() } 414 | } 415 | } 416 | 417 | impl<'a, A: ?Sized + Downcast, V: IntoBox> VacantEntry<'a, A, V> { 418 | /// Sets the value of the entry with the VacantEntry's key, 419 | /// and returns a mutable reference to it 420 | #[inline] 421 | pub fn insert(self, value: V) -> &'a mut V { 422 | unsafe { self.inner.insert(value.into_box()).downcast_mut_unchecked() } 423 | } 424 | } 425 | 426 | #[cfg(test)] 427 | mod tests { 428 | use crate::CloneAny; 429 | use super::*; 430 | 431 | #[derive(Clone, Debug, PartialEq)] struct A(i32); 432 | #[derive(Clone, Debug, PartialEq)] struct B(i32); 433 | #[derive(Clone, Debug, PartialEq)] struct C(i32); 434 | #[derive(Clone, Debug, PartialEq)] struct D(i32); 435 | #[derive(Clone, Debug, PartialEq)] struct E(i32); 436 | #[derive(Clone, Debug, PartialEq)] struct F(i32); 437 | #[derive(Clone, Debug, PartialEq)] struct J(i32); 438 | 439 | macro_rules! test_entry { 440 | ($name:ident, $init:ty) => { 441 | #[test] 442 | fn $name() { 443 | let mut map = <$init>::new(); 444 | assert_eq!(map.insert(A(10)), None); 445 | assert_eq!(map.insert(B(20)), None); 446 | assert_eq!(map.insert(C(30)), None); 447 | assert_eq!(map.insert(D(40)), None); 448 | assert_eq!(map.insert(E(50)), None); 449 | assert_eq!(map.insert(F(60)), None); 450 | 451 | // Existing key (insert) 452 | match map.entry::() { 453 | Entry::Vacant(_) => unreachable!(), 454 | Entry::Occupied(mut view) => { 455 | assert_eq!(view.get(), &A(10)); 456 | assert_eq!(view.insert(A(100)), A(10)); 457 | } 458 | } 459 | assert_eq!(map.get::().unwrap(), &A(100)); 460 | assert_eq!(map.len(), 6); 461 | 462 | 463 | // Existing key (update) 464 | match map.entry::() { 465 | Entry::Vacant(_) => unreachable!(), 466 | Entry::Occupied(mut view) => { 467 | let v = view.get_mut(); 468 | let new_v = B(v.0 * 10); 469 | *v = new_v; 470 | } 471 | } 472 | assert_eq!(map.get::().unwrap(), &B(200)); 473 | assert_eq!(map.len(), 6); 474 | 475 | 476 | // Existing key (remove) 477 | match map.entry::() { 478 | Entry::Vacant(_) => unreachable!(), 479 | Entry::Occupied(view) => { 480 | assert_eq!(view.remove(), C(30)); 481 | } 482 | } 483 | assert_eq!(map.get::(), None); 484 | assert_eq!(map.len(), 5); 485 | 486 | 487 | // Inexistent key (insert) 488 | match map.entry::() { 489 | Entry::Occupied(_) => unreachable!(), 490 | Entry::Vacant(view) => { 491 | assert_eq!(*view.insert(J(1000)), J(1000)); 492 | } 493 | } 494 | assert_eq!(map.get::().unwrap(), &J(1000)); 495 | assert_eq!(map.len(), 6); 496 | 497 | // Entry.or_insert on existing key 498 | map.entry::().or_insert(B(71)).0 += 1; 499 | assert_eq!(map.get::().unwrap(), &B(201)); 500 | assert_eq!(map.len(), 6); 501 | 502 | // Entry.or_insert on nonexisting key 503 | map.entry::().or_insert(C(300)).0 += 1; 504 | assert_eq!(map.get::().unwrap(), &C(301)); 505 | assert_eq!(map.len(), 7); 506 | } 507 | } 508 | } 509 | 510 | test_entry!(test_entry_any, AnyMap); 511 | test_entry!(test_entry_cloneany, Map); 512 | 513 | #[test] 514 | fn test_default() { 515 | let map: AnyMap = Default::default(); 516 | assert_eq!(map.len(), 0); 517 | } 518 | 519 | #[test] 520 | fn test_clone() { 521 | let mut map: Map = Map::new(); 522 | let _ = map.insert(A(1)); 523 | let _ = map.insert(B(2)); 524 | let _ = map.insert(D(3)); 525 | let _ = map.insert(E(4)); 526 | let _ = map.insert(F(5)); 527 | let _ = map.insert(J(6)); 528 | let map2 = map.clone(); 529 | assert_eq!(map2.len(), 6); 530 | assert_eq!(map2.get::(), Some(&A(1))); 531 | assert_eq!(map2.get::(), Some(&B(2))); 532 | assert_eq!(map2.get::(), None); 533 | assert_eq!(map2.get::(), Some(&D(3))); 534 | assert_eq!(map2.get::(), Some(&E(4))); 535 | assert_eq!(map2.get::(), Some(&F(5))); 536 | assert_eq!(map2.get::(), Some(&J(6))); 537 | } 538 | 539 | #[test] 540 | fn test_varieties() { 541 | fn assert_send() { } 542 | fn assert_sync() { } 543 | fn assert_clone() { } 544 | fn assert_debug() { } 545 | assert_send::>(); 546 | assert_send::>(); 547 | assert_sync::>(); 548 | assert_debug::>(); 549 | assert_debug::>(); 550 | assert_debug::>(); 551 | assert_send::>(); 552 | assert_send::>(); 553 | assert_sync::>(); 554 | assert_clone::>(); 555 | assert_clone::>(); 556 | assert_clone::>(); 557 | assert_debug::>(); 558 | assert_debug::>(); 559 | assert_debug::>(); 560 | } 561 | 562 | #[test] 563 | fn test_extend() { 564 | let mut map = AnyMap::new(); 565 | // (vec![] for 1.36.0 compatibility; more recently, you should use [] instead.) 566 | #[cfg(not(feature = "std"))] 567 | use alloc::vec; 568 | map.extend(vec![Box::new(123) as Box, Box::new(456), Box::new(true)]); 569 | assert_eq!(map.get(), Some(&456)); 570 | assert_eq!(map.get::(), Some(&true)); 571 | assert!(map.get::>().is_none()); 572 | } 573 | } 574 | }; 575 | } 576 | 577 | #[cfg(feature = "std")] 578 | everything!( 579 | "let mut data = anymap::AnyMap::new();", 580 | std::collections 581 | ); 582 | 583 | #[cfg(feature = "hashbrown")] 584 | /// AnyMap backed by `hashbrown`. 585 | /// 586 | /// This depends on the `hashbrown` Cargo feature being enabled. 587 | pub mod hashbrown { 588 | use crate::TypeIdHasher; 589 | #[cfg(doc)] 590 | use crate::any::CloneAny; 591 | 592 | everything!( 593 | "let mut data = anymap::hashbrown::AnyMap::new();", 594 | hashbrown, 595 | BuildHasherDefault 596 | ); 597 | } 598 | 599 | /// A hasher designed to eke a little more speed out, given `TypeId`’s known characteristics. 600 | /// 601 | /// Specifically, this is a no-op hasher that expects to be fed a u64’s worth of 602 | /// randomly-distributed bits. It works well for `TypeId` (eliminating start-up time, so that my 603 | /// get_missing benchmark is ~30ns rather than ~900ns, and being a good deal faster after that, so 604 | /// that my insert_and_get_on_260_types benchmark is ~12μs instead of ~21.5μs), but will 605 | /// panic in debug mode and always emit zeros in release mode for any other sorts of inputs, so 606 | /// yeah, don’t use it! 😀 607 | #[derive(Default)] 608 | pub struct TypeIdHasher { 609 | value: u64, 610 | } 611 | 612 | impl Hasher for TypeIdHasher { 613 | #[inline] 614 | fn write(&mut self, bytes: &[u8]) { 615 | // This expects to receive exactly one 64-bit value, and there’s no realistic chance of 616 | // that changing, but I don’t want to depend on something that isn’t expressly part of the 617 | // contract for safety. But I’m OK with release builds putting everything in one bucket 618 | // if it *did* change (and debug builds panicking). 619 | debug_assert_eq!(bytes.len(), 8); 620 | let _ = bytes.try_into() 621 | .map(|array| self.value = u64::from_ne_bytes(array)); 622 | } 623 | 624 | #[inline] 625 | fn finish(&self) -> u64 { self.value } 626 | } 627 | 628 | #[test] 629 | fn type_id_hasher() { 630 | #[cfg(not(feature = "std"))] 631 | use alloc::vec::Vec; 632 | use core::hash::Hash; 633 | use core::any::TypeId; 634 | fn verify_hashing_with(type_id: TypeId) { 635 | let mut hasher = TypeIdHasher::default(); 636 | type_id.hash(&mut hasher); 637 | // SAFETY: u64 is valid for all bit patterns. 638 | assert_eq!(hasher.finish(), unsafe { core::mem::transmute::(type_id) }); 639 | } 640 | // Pick a variety of types, just to demonstrate it’s all sane. Normal, zero-sized, unsized, &c. 641 | verify_hashing_with(TypeId::of::()); 642 | verify_hashing_with(TypeId::of::<()>()); 643 | verify_hashing_with(TypeId::of::()); 644 | verify_hashing_with(TypeId::of::<&str>()); 645 | verify_hashing_with(TypeId::of::>()); 646 | } 647 | -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | export RUSTFLAGS="-D warnings" 4 | export RUSTDOCFLAGS="-D warnings" 5 | run_tests() { 6 | for release in "" "--release"; do 7 | 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. ¯\_(ツ)_/¯) 8 | cargo $1 test $release --no-default-features --features hashbrown 9 | cargo $1 test $release 10 | cargo $1 test $release --all-features 11 | done 12 | } 13 | 14 | # We’d like to test with the oldest declared-supported version of *all* our dependencies. 15 | # That means Rust 1.36.0 + hashbrown 0.1.1. 16 | # Hence the different lock file. 17 | # (Also Rust 1.36.0 can’t read the latest lock file format.) 18 | cp test-oldest-Cargo.lock Cargo.lock 19 | run_tests +1.36.0 20 | rm Cargo.lock 21 | run_tests 22 | 23 | cargo clippy 24 | cargo bench 25 | cargo doc 26 | -------------------------------------------------------------------------------- /test-oldest-Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "anymap" 5 | version = "1.0.0-beta.1" 6 | dependencies = [ 7 | "hashbrown 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 8 | ] 9 | 10 | [[package]] 11 | name = "byteorder" 12 | version = "1.4.3" 13 | source = "registry+https://github.com/rust-lang/crates.io-index" 14 | 15 | [[package]] 16 | name = "hashbrown" 17 | version = "0.1.1" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | dependencies = [ 20 | "byteorder 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 21 | "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 22 | ] 23 | 24 | [[package]] 25 | name = "scopeguard" 26 | version = "0.3.3" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | 29 | [metadata] 30 | "checksum byteorder 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 31 | "checksum hashbrown 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2dfb69c301cead891d010536c7d1b3affe792f5a2be5d6fbd0fcaf7fa0976962" 32 | "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" 33 | --------------------------------------------------------------------------------