fixed_typemap/
lib.rs

1//! The last typemap you'll ever need.
2//!
3//! Sometimes, you want a map where the keys are types.  There are a variety of approaches to this, but they all boil
4//! down to `HashMap<TypeId, Box<Any>>`. or a similar structure.  This has two problems: (1) you're always hashing your
5//! types, and (2) you can't do nice things like iterate over it by traits.  This map solves both problems through the
6//! [decl_fixed_typemap] proc macro.  Features:
7//!
8//! - Ability to specify a fixed list of types, which will be allocated inline.
9//!   - these can be accessed with no overhead via `get_infallible`, which compiles down to a simple struct field borrow
10//!     `&mymap.magic_field_name`.
11//!   - They can also be accessed by `get`, which works additionally in dynamic contexts.
12//!   - Or via the [InfallibleKey] trait, for dynamic code which wishes to accept any map that is known to contain some
13//!     type.
14//! - Ability to name fields of the generated struct, and to forward attributes (e.g. you can tag things with serde).
15//! - If not using support for dynamic typemaps, no allocation.
16//!   - In theory also `no_std` but I don't know enough about that to be sure I'm testing it right; if you want to help,
17//!       finishing it will take about an hour.
18//! - Ability to declare a list of traits you want to iterate by.  Mutable iteration is supported, and the returned
19//!   iterators don't require boxing.
20//! - As a consequence of no allocation, fixed maps don't pointer chase and are as big as the combined types.
21//!
22//! The big limitation is no deletion from the map.  This doesn't make sense--how do you "delete" a field in a fixed map
23//! of types?  We could probably define behavior here, but it can be emulated storing `Option` in the map.
24//!
25//! As motivation, I wrote this to be used in an ECS which needs to allocate hundreds or thousands of typemaps for
26//! component stores.  It can also be used in places where you need to fake being generic over structs which have
27//! specific field names by instead using a typemap build with this crate, naming your fields, and then using newtypes
28//! to enable generic functions.  The no_std story is also almost there, but I don't personally need that and so
29//! finishing that up depends on interest (read: I will if you ask and are willing to help out).
30//!
31//! # Quickstart
32//!
33//! See also the [example] module for what the generated code looks like.
34//!
35//! Let's suppose we want to make a plugin system.  We might do it like the following, which demonstrates most of the
36//! features provided by generated maps:
37//!
38//! ```rust
39//! use fixed_typemap::decl_fixed_typemap;
40//!
41//! // First, define a trait to represent a plugin:
42//! trait Plugin {
43//!     fn run(&self);
44//! }
45//!
46//! // And now we do some plugin types.  We give these a `u64` value so we can demonstrate mutation.
47//! #[derive(Default)]
48//! struct GraphicsPlugin(u64);
49//!
50//! #[derive(Default)]
51//! struct SoundPlugin(u64);
52//!
53//! #[derive(Default)]
54//! struct NetworkingPlugin(u64);
55//!
56//! #[derive(Default)]
57//! struct UserProvidedPlugin(u64);
58//!
59//! impl Plugin for GraphicsPlugin {
60//!     fn run(&self) {
61//!         println!("Running graphics: {}", self.0);
62//!     }
63//! }
64//!
65//! impl Plugin for SoundPlugin {
66//!     fn run(&self) {
67//!         println!("Running sound: {}", self.0);
68//!     }
69//! }
70//!
71//! impl Plugin for NetworkingPlugin {
72//!     fn run(&self) {
73//!         println!("Running networking: {}", self.0);
74//!     }
75//! }
76//!
77//! impl Plugin for UserProvidedPlugin {
78//!     fn run(&self) {
79//!         println!("Running user-supplied code: {}", self.0);
80//!     }
81//! }
82//!
83//! // Some plugins are always present, so we put them in the fixed part of the typemap.  But we can also have a dynamic
84//! // section, which is where user-provided values can go.
85//! //
86//! // Another way to let users install their own plugins, not demonstrated here, is to define a macro that builds typemaps
87//! // and then be generic over the kind of map provided using the InfallibleKey trait or IterableAs.
88//! decl_fixed_typemap! {
89//!     // We want our typemap to be dynamic, because we have an open set of user-specified values.  If we didn't specify
90//!     // that attribute, insert would fail on new values not declared here.
91//!     //
92//!     // We also want to be able to iterate over our plugins to do things with them, so we ask fixed_typemap to give us a
93//!     // helper method.  It will generate `iter_plugins` and `iter_plugins_mut` for us, as well as an implementation of
94//!     // `IterableAs` to be used in generic code.
95//!     #[fixed_typemap(dynamic, iterable_traits(Plugin = "iter_plugins"))]
96//!     struct PluginMap {
97//!         // Let's say that graphics is really important, and we want a convenient name.  It would also be possible to get
98//!         // this without overhead via `get_infallible`, but sometimes names are convenient.
99//!         graphics: GraphicsPlugin,
100//!         // But we don't care about the names of the rest, because we'll only access them infrequently.
101//!         _: SoundPlugin,
102//!         // let's give networking a different starting value:
103//!         _: NetworkingPlugin = NetworkingPlugin(100),
104//!     }
105//! }
106//!
107//! // We can run plugins via simple iteration:
108//! fn run_plugins(map: &PluginMap) {
109//!     for p in map.iter_plugins() {
110//!         p.run();
111//!     }
112//! }
113//!
114//! fn main() {
115//!     // Build our typemap:
116//!     let mut map = PluginMap::new();
117//!
118//!     // Now, we have everything that is in the fixed part of the map. So:
119//!     println!("Before adding user-provided plugin");
120//!     run_plugins(&map);
121//!
122//!     // And we want to add one provided by our user.  Insert fails on fixed typemaps, when the type provided isn't in the
123//!     // map, but is otherwise like std collections: either add a new value or replace.
124//!     map.insert(UserProvidedPlugin(0))
125//!         .expect("In this context, insert should always succeed");
126//!
127//!     println!("After user-provided plugin");
128//!     run_plugins(&map);
129//!
130//!     // Now let's modify some.  Graphics is named:
131//!     map.graphics = GraphicsPlugin(1);
132//!
133//!     // Sound and networking are infallible at the type system level, so we can get them without going through `Option`:
134//!     *map.get_infallible_mut::<SoundPlugin>() = SoundPlugin(2);
135//!
136//!     // insert also updates:
137//!     map.insert(NetworkingPlugin(10))
138//!         .expect("Insert should always succeed in this context");
139//!
140//!     // For the dynamic part of the map, we get back option and must go through the slower fallible getters.  We know it
141//!     // can't fail here and this is also an example, so let's just unwrap:
142//!     *map.get_mut::<UserProvidedPlugin>().unwrap() = UserProvidedPlugin(20);
143//!
144//!     println!("After modification");
145//!     run_plugins(&map);
146//! }
147//! ```
148//!
149//! # So wait, how is this implemented?
150//!
151//! The trick here is that for infallible accesses, we can hide the borrow behind [InfallibleKey] and use the fact that
152//! this is a macro to punch out a bunch of impls.  For fallible accesses, we hide the access behind some unsafe pointer
153//! manipulation and a comparison with `TypeId` before falling back to a `HashMap`.  The if tree required is in theory
154//! const, but Rust doesn't yet offer const `TypeId` so we can't yet make a strong guarantee.
155//!
156//! Trait iteration is done by storing the dynamic part of the map behind a generated cell type, which contains a boxed
157//! value and a number of function pointers that look roughly like the following:
158//!
159//! ```ignore
160//! fn convert::<ContainedT>(&Any) -> &dyn TargetTrait {
161//!     // Convert to the contained type, then to a trait object.
162//! }
163//! ```
164//!
165//! Then iteration chains a fixed-sized array containing trait objects for the static part and an iterator over the map
166//! and gives that back.  For fixed maps we instead chain to `Empty`, but in either case the iterator is entirely
167//! allocated on the stack, the static part being `[dyn TargetTrait; field_count]` in size.
168//!
169//! # The Macro and What We can generate
170//!
171//! The macro takes a struct-like syntax.  The struct must not contain generics or lifetime parameters.  Attributes on
172//! the struct are forwarded to the final struct, though care should be taken: if you're not naming all the fields
173//! explicitly for example, then chances are `Serde` won't do what you want.  The syntax of a field is:
174//!
175//! ```ignore
176//! (_ | ident): type [= expr],
177//! ```
178//!
179//! The extensions here being `_` as a field name when you don't care about the name, and `= expression` to specify a
180//! default value.  The macro requires that all fields either impl `Default` or have a provided expression.
181//!
182//! The `fixed_typemap` attribute can be used to control the generated struct:
183//!
184//! - `#[fixed_typemap(dynamic)]`: this typemap will have a dynamic section and can consequently hold any type. Requires
185//!   allocation.
186//! - `#[fixed_typemap(iterable_traits(path = "method_name", ... ))]`: generate a `method_name` and `method_name_mut`
187//!   trait pair which will iterate over the specified trait, as well as the appropriate [IterableAs] implementations.
188pub mod example;
189
190pub use fixed_typemap_internals::{InfallibleKey, IterableAs};
191pub use fixed_typemap_macros::*;