fixed_map/lib.rs
1//! [<img alt="github" src="https://img.shields.io/badge/github-udoprog/fixed--map-8da0cb?style=for-the-badge&logo=github" height="20">](https://github.com/udoprog/fixed-map)
2//! [<img alt="crates.io" src="https://img.shields.io/crates/v/fixed-map.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/fixed-map)
3//! [<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-fixed--map-66c2a5?style=for-the-badge&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K" height="20">](https://docs.rs/fixed-map)
4//!
5//! This crate provides a [`Map`] and [`Set`] container that can make use of a
6//! pre-calculated backing storage. This enables the Rust compiler to heavily
7//! optimize operations over them and avoid allocating.
8//!
9//! See [documentation] for information on how to use this crate.
10//!
11//! <br>
12//!
13//! ## Usage
14//!
15//! Add `fixed-map` to your `Cargo.toml`:
16//!
17//! ```toml
18//! [dependencies]
19//! fixed-map = "0.9.6"
20//! ```
21//!
22//! Anything used as a key in either a [`Map`] or a [`Set`] needs to implement
23//! the [`Key`] trait. This should be derived:
24//!
25//! ```
26//! use fixed_map::{Key, Map};
27//!
28//! #[derive(Clone, Copy, Key)]
29//! enum MyKey {
30//! North,
31//! South,
32//! East,
33//! West,
34//! }
35//! ```
36//!
37//! After this you can use one of our containers:
38//!
39//! ```
40//! # #[derive(Clone, Copy, fixed_map::Key)]
41//! # enum MyKey { North, South, East, West }
42//! use fixed_map::{Map, Set};
43//!
44//! let mut map = Map::new();
45//! map.insert(MyKey::North, 200);
46//! map.insert(MyKey::South, 100);
47//!
48//! assert_eq!(map.get(MyKey::North), Some(&200));
49//! assert_eq!(map.get(MyKey::East), None);
50//!
51//! let mut set = Set::new();
52//! set.insert(MyKey::North);
53//! set.insert(MyKey::South);
54//!
55//! assert!(set.contains(MyKey::South));
56//! assert!(!set.contains(MyKey::East));
57//! ```
58//!
59//! <br>
60//!
61//! ## Features
62//!
63//! The following features are available:
64//!
65//! * `std` - Disabling this feature causes this crate to be no-std. This means
66//! that dynamic types cannot be used in keys, like ones enabled by the `map`
67//! feature (default).
68//! * `hashbrown` - Causes [`Storage`] to be implemented by dynamic types such
69//! as `&'static str` or `u32`. These are backed by a `hashbrown` (default).
70//! * `entry` - Enables an [`entry`] API similar to that found on [`HashMap`].
71//! * `serde` - Causes [`Map`] and [`Set`] to implement [`Serialize`] and
72//! [`Deserialize`] if it's implemented by the key and value.
73//! * `musli02` - Causes [`Map`] and [`Set`] to implement `Encode` and `Decode`
74//! from [`musli`] 0.2 if it's implemented by the key and value. A map is
75//! encoded as a map and a set as a sequence, including `#[key(bitset)]` sets.
76//! * `musli-zerocopy01` - Causes [`Set`] to implement `ZeroCopy` from
77//! [`musli-zerocopy`] 0.1 when the key uses `#[key(bitset)]`, with the same
78//! layout as its raw value.
79//!
80//! The `musli02` and `musli-zerocopy01` features need Rust 1.85, the minimum
81//! supported version of those crates.
82//!
83//! <br>
84//!
85//! ## Specialized storage through the [`Key`] trait
86//!
87//! The [`Key` derive] is provided to instruct our containers on how to build
88//! optimized storage for a given [`Key`]. We also require any key to be [`Copy`].
89//!
90//! ```
91//! use fixed_map::Key;
92//!
93//! #[derive(Clone, Copy, Key)]
94//! enum MyKey {
95//! North,
96//! South,
97//! East,
98//! West,
99//! }
100//! ```
101//!
102//! What happens behind the scenes is that a proc macro is used to build a
103//! struct optimized for storing and indexing exactly 4 values - one for each
104//! variant.
105//!
106//! Something exactly like this:
107//!
108//! ```no_run
109//! struct Storage<V> {
110//! data: [Option<V>; 4],
111//! }
112//! ```
113//!
114//! It becomes a bit more complicated once we start considering *composite
115//! keys*. See the [`Key`] documentation for more information.
116//!
117//! <br>
118//!
119//! ## Why does this crate exist?
120//!
121//! There are many cases where you want associate a value with a small, fixed
122//! number of elements identified by an enum.
123//!
124//! Let's say you have a game where each room has something in four directions.
125//! We can model this relationship between the direction and the item using two
126//! enums.
127//!
128//! ```
129//! #[repr(usize)]
130//! pub enum Dir {
131//! North,
132//! East,
133//! South,
134//! West,
135//! }
136//!
137//! pub enum Item {
138//! Bow,
139//! Sword,
140//! Axe,
141//! }
142//! ```
143//!
144//! The goal is for the performance of fixed map to be identical to storing the
145//! data linearly in memory like you could through an array like `[Option<Item>;
146//! N]` where each *index* corresponds to a variant in `Dir`.
147//!
148//! Doing this manually could look like this:
149//!
150//! ```
151//! # #[repr(usize)]
152//! # pub enum Dir { North, East, South, West }
153//! # #[derive(Debug)]
154//! # pub enum Item { Bow, Sword, Axe }
155//! let mut map: [Option<Item>; 4] = [None, None, None, None];
156//! map[Dir::North as usize] = Some(Item::Bow);
157//!
158//! if let Some(item) = &map[Dir::North as usize] {
159//! println!("found item: {:?}", item);
160//! }
161//! ```
162//!
163//! But with a fixed [`Map`] you can do it idiomatically like this, without
164//! incurring a drop in performance:
165//!
166//! ```
167//! use fixed_map::{Key, Map};
168//!
169//! #[derive(Clone, Copy, Key)]
170//! pub enum Dir {
171//! North,
172//! East,
173//! South,
174//! West,
175//! }
176//!
177//! #[derive(Debug)]
178//! pub enum Item {
179//! Bow,
180//! Sword,
181//! Axe,
182//! }
183//!
184//! let mut map = Map::new();
185//! map.insert(Dir::North, Item::Bow);
186//!
187//! if let Some(item) = map.get(Dir::North) {
188//! println!("found item: {:?}", item);
189//! }
190//! ```
191//!
192//! <br>
193//!
194//! ## Unsafe use
195//!
196//! The Entry API uses `unwrap_unchecked` to obtain mutable references to the
197//! inner value of `Some`s, and to skip `drop` when overwriting `None`s.
198//!
199//! <br>
200//!
201//! ## Benchmarks
202//!
203//! We include benchmarks to ensure that we abide by the expectation that a
204//! fixed map or set should perform roughly the same as an array with the same
205//! number of elements.
206//!
207//! In the following benchmarks fixed-map is compared to:
208//!
209//! * `fixed` - A [`Map`] with a derived [`Key`] with `N` variants.
210//! * [`hashbrown`] - A high performance hash map. This is only included for
211//! reference.
212//! - Note: Maps are created with `HashMap::with_capacity(N)`.
213//! * `array` - A simple `[Option<Key>; N]` array.
214//!
215//! Note: for all `insert` benchmarks the underlying storage is cloned in each
216//! iteration.
217//!
218//! ```text
219//! get/fixed/4 time: [208.96 ps 209.57 ps 210.17 ps]
220//! get/fixed/8 time: [211.12 ps 211.86 ps 212.55 ps]
221//! get/fixed/16 time: [211.50 ps 211.84 ps 212.23 ps]
222//! get/fixed/32 time: [211.02 ps 211.40 ps 211.79 ps]
223//! get/array/4 time: [215.76 ps 216.56 ps 217.68 ps]
224//! get/array/8 time: [216.80 ps 217.28 ps 217.83 ps]
225//! get/array/16 time: [215.88 ps 216.21 ps 216.58 ps]
226//! get/array/32 time: [216.39 ps 216.82 ps 217.33 ps]
227//! get/hashbrown/4 time: [2.9134 ns 2.9168 ns 2.9210 ns]
228//! get/hashbrown/8 time: [2.9143 ns 2.9175 ns 2.9212 ns]
229//! get/hashbrown/16 time: [2.9258 ns 2.9293 ns 2.9328 ns]
230//! get/hashbrown/32 time: [2.9387 ns 2.9428 ns 2.9466 ns]
231//!
232//! insert/fixed/4 time: [421.82 ps 422.47 ps 423.22 ps]
233//! insert/fixed/8 time: [635.46 ps 636.91 ps 638.55 ps]
234//! insert/fixed/16 time: [1.0579 ns 1.0599 ns 1.0621 ns]
235//! insert/fixed/32 time: [1.6991 ns 1.7016 ns 1.7043 ns]
236//! insert/array/4 time: [419.26 ps 419.76 ps 420.30 ps]
237//! insert/array/8 time: [624.30 ps 626.27 ps 628.33 ps]
238//! insert/array/16 time: [1.0444 ns 1.0467 ns 1.0490 ns]
239//! insert/array/32 time: [1.6828 ns 1.6904 ns 1.6990 ns]
240//! insert/hashbrown/4 time: [87.002 ns 87.233 ns 87.475 ns]
241//! insert/hashbrown/8 time: [96.995 ns 97.287 ns 97.589 ns]
242//! insert/hashbrown/16 time: [517.89 ns 518.66 ns 519.57 ns]
243//! insert/hashbrown/32 time: [156.10 ns 156.67 ns 157.30 ns]
244//!
245//! values/fixed/4 time: [209.09 ps 209.51 ps 209.91 ps]
246//! values/fixed/8 time: [213.99 ps 215.34 ps 217.08 ps]
247//! values/fixed/16 time: [213.24 ps 213.94 ps 214.72 ps]
248//! values/fixed/32 time: [212.71 ps 213.82 ps 215.15 ps]
249//! values/array/4 time: [211.07 ps 211.78 ps 212.59 ps]
250//! values/array/8 time: [211.48 ps 212.03 ps 212.65 ps]
251//! values/array/16 time: [213.04 ps 213.49 ps 213.99 ps]
252//! values/array/32 time: [213.18 ps 213.78 ps 214.60 ps]
253//! values/hashbrown/4 time: [3.3965 ns 3.4007 ns 3.4056 ns]
254//! values/hashbrown/8 time: [3.8443 ns 3.8627 ns 3.8895 ns]
255//! values/hashbrown/16 time: [5.6312 ns 5.6666 ns 5.7029 ns]
256//! values/hashbrown/32 time: [8.7221 ns 8.7674 ns 8.8117 ns]
257//!
258//! array/sum_values time: [3.0394 ns 3.0463 ns 3.0534 ns]
259//! fixed/sum_values time: [3.0503 ns 3.0559 ns 3.0619 ns]
260//! ```
261//!
262//! <br>
263//!
264//! ## Examples
265//!
266//! Most examples are in place to test what kind of assembler they compile to.
267//!
268//! To do this, run:
269//!
270//! ```sh
271//! RUSTFLAGS="--emit asm" cargo build --release --example <example>
272//! ```
273//!
274//! You should be able to find the assembler generated in the target folder:
275//!
276//! ```sh
277//! ls target/release/examples/
278//! ```
279//!
280//! [`Copy`]: https://doc.rust-lang.org/std/marker/trait.Copy.html
281//! [`Deserialize`]: https://docs.rs/serde/1/serde/trait.Deserialize.html
282//! [`musli`]: https://docs.rs/musli/0.2
283//! [`musli-zerocopy`]: https://docs.rs/musli-zerocopy/0.1
284//! [`hashbrown`]: https://github.com/Amanieu/hashbrown
285//! [`Key` derive]: https://docs.rs/fixed-map/latest/fixed_map/derive.Key.html
286//! [`Key`]: https://docs.rs/fixed-map/latest/fixed_map/derive.Key.html
287//! [`Map`]: https://docs.rs/fixed-map/latest/fixed_map/map/struct.Map.html
288//! [`entry`]: https://docs.rs/fixed-map/latest/fixed_map/map/struct.Map.html#method.entry
289//! [`HashMap`]: https://doc.rust-lang.org/stable/std/collections/hash_map/struct.HashMap.html#method.entry
290//! [`Serialize`]: https://docs.rs/serde/1/serde/trait.Serialize.html
291//! [`Set`]: https://docs.rs/fixed-map/latest/fixed_map/set/struct.Set.html
292//! [`Storage`]: https://docs.rs/fixed-map/latest/fixed_map/storage/trait.Storage.html
293//! [documentation]: https://docs.rs/fixed-map
294
295#![no_std]
296#![deny(missing_docs)]
297#![allow(clippy::expl_impl_clone_on_copy)]
298#![allow(clippy::module_name_repetitions)]
299#![allow(clippy::type_repetition_in_bounds)]
300
301pub mod raw;
302
303mod key;
304pub use self::key::Key;
305
306pub mod map;
307#[doc(inline)]
308pub use self::map::Map;
309
310pub mod set;
311#[doc(inline)]
312pub use self::set::Set;
313
314// Re-export the option bucket types for use in `derive(Key)`
315#[doc(hidden)]
316pub mod option_bucket;
317
318#[doc(hidden)]
319pub mod macro_support;
320
321/// Derive to implement the [`Key`] trait.
322///
323/// This derive implements the [`Key`] trait for a given type.
324///
325/// The [`Key`] trait is what allows `fixed_map` to set up storage for a type
326/// that will be the key in a fixed map.
327///
328/// > Note: this requires the `::fixed_map` crate to be in scope when it's
329/// > derived.
330///
331/// <br>
332///
333/// ## Container attributes
334///
335/// <br>
336///
337/// Container attributes are attributes which apply directly onto container
338/// types. For [`Key`] this is just enums.
339///
340/// <br>
341///
342/// #### `#[key(bitset)]`
343///
344/// This ensures that backing storage is performed with a bitset when used with
345/// a [`Set`].
346///
347/// ```
348/// use fixed_map::{Key, Set};
349///
350/// #[derive(Clone, Copy, Key)]
351/// pub enum Regular {
352/// First,
353/// Second,
354/// Third,
355/// }
356///
357/// #[derive(Clone, Copy, Key)]
358/// #[key(bitset)]
359/// pub enum Bits {
360/// First,
361/// Second,
362/// Third,
363/// }
364///
365/// // Normal storage uses an array of booleans:
366/// assert_eq!(core::mem::size_of::<Set<Regular>>(), 3);
367///
368/// // Bitset storage uses a single u8 (or other appropriate type based on size):
369/// assert_eq!(core::mem::size_of::<Set<Bits>>(), 1);
370/// ```
371///
372/// > **Note:** not all operations will be implemented when this attribute is
373/// > present, so some container methods might not work.
374///
375/// <br>
376///
377/// ## Guide
378///
379/// Given the following enum:
380///
381/// ```
382/// use fixed_map::Key;
383///
384/// #[derive(Clone, Copy, Key)]
385/// pub enum MyKey {
386/// First,
387/// Second,
388/// Third,
389/// }
390/// ```
391///
392/// It performs the following expansion:
393///
394/// ```ignore
395/// use fixed_map::Key;
396///
397/// #[derive(Clone, Copy)]
398/// pub enum MyKey {
399/// First,
400/// Second,
401/// Third,
402/// }
403///
404/// /// Build a storage struct containing an item for each key:
405/// pub struct MyKeyMapStorage<V> {
406/// /// Storage for `MyKey::First`.
407/// f1: Option<V>,
408/// /// Storage for `MyKey::Second`.
409/// f2: Option<V>,
410/// /// Storage for `MyKey::Third`.
411/// f3: Option<V>,
412/// }
413///
414/// /// Build a storage struct containing an element for each key:
415/// pub struct MyKeySetStorage {
416/// data: [bool; 3],
417/// }
418///
419/// /// Implement map storage for key.
420/// impl<V> ::fixed_map::map::MapStorage<MyKey, V> for MyKeyMapStorage<V> {
421/// fn get(&self, key: MyKey) -> Option<&V> {
422/// match key {
423/// MyKey::First => self.f1.as_ref(),
424/// MyKey::Second => self.f2.as_ref(),
425/// MyKey::Third => self.f3.as_ref(),
426/// }
427/// }
428///
429/// /* skipped */
430/// }
431///
432/// /// Implement set storage for key.
433/// impl ::fixed_map::set::SetStorage<MyKey> for MyKeySetStorage {
434/// fn contains(&self, key: MyKey) -> bool {
435/// let [a, b, c] = &self.data;
436///
437/// match key {
438/// MyKey::First => *a,
439/// MyKey::Second => *b,
440/// MyKey::Third => *c,
441/// }
442/// }
443///
444/// /* skipped */
445/// }
446///
447/// impl<V> Default for MyKeyMapStorage<V> {
448/// fn default() -> Self {
449/// Self {
450/// f1: None,
451/// f2: None,
452/// f3: None,
453/// }
454/// }
455/// }
456///
457/// impl Default for MyKeySetStorage {
458/// fn default() -> Self {
459/// Self {
460/// data: [false, false, false]
461/// }
462/// }
463/// }
464///
465/// /// Implement the Key trait to point out storage.
466/// impl ::fixed_map::Key for MyKey {
467/// type MapStorage<V> = MyKeyMapStorage<V>;
468/// type SetStorage = MyKeySetStorage;
469/// }
470/// ```
471#[doc(inline)]
472pub use fixed_map_derive::Key;