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