tagged_box/
lib.rs

1#![no_std]
2
3//! [![Crates.io](https://img.shields.io/crates/v/tagged-box?style=flat)](https://crates.io/crates/tagged-box)
4//! [![Docs.rs](https://docs.rs/tagged-box/badge.svg)](https://docs.rs/tagged-box)
5//! [![GitHub](https://img.shields.io/github/languages/top/Kixiron/tagged-box)](https://github.com/Kixiron/tagged-box)
6//! [![LOC](https://tokei.rs/b1/github/Kixiron/tagged-box)](https://github.com/Kixiron/tagged-box)
7//!
8//! A `no_std`, zero-dependency crate for the creation and management of NaN-boxed types with
9//! [`Box`]-like semantics and tagged pointers, tagged pointers and a macro interface to safely
10//! create NaN-boxed enums.
11//!
12//! ## Quickstart
13//!
14//! First, add the crate to your `Cargo.lock` (Note: for variable reserved widths, see the [features] section)
15//!
16//! ```toml
17//! tagged_box = "0.1.0"
18//! ```
19//!
20//! The recommend way to use this crate is through the [`tagged_box!`] macro, as it's a safe
21//! interface for using NaN-boxed enums.
22//!
23//! Next, for using the [`tagged_box!`] macro, add the following to the top of your file
24//!
25//! ```rust
26//! use tagged_box::{tagged_box, TaggableContainer, TaggableInner};
27//! ```
28//!
29//! Then you can use the macro as follows
30//!
31//! ```rust
32//! # extern crate alloc;
33//! # use alloc::string::String;
34//! # use tagged_box::{tagged_box, TaggableContainer};
35//! tagged_box! {
36//!     #[derive(Debug, Clone, PartialEq)]
37//!     struct Container, enum Item {
38//!         Integer(i32),
39//!         Boolean(bool),
40//!         String(String),
41//!     }
42//! }
43//!
44//! let container = Container::from(String::from("Hello from tagged-box!"));
45//!
46//! assert_eq!(
47//!     container.into_inner(),
48//!     Item::String(String::from("Hello from tagged-box!"))
49//! );
50//! ```
51//!
52//! For working with NaN-boxes or [`TaggedBox`]es, simply add
53//!
54//! ```rust
55//! use tagged_box::TaggedBox;
56//! ```
57//!
58//! And for [`TaggedPointer`]s use
59//!
60//! ```rust
61//! use tagged_box::TaggedPointer;
62//! ```
63//!
64//! ## What this crate does
65//!
66//! This crate implements [NaN-Boxing] and [Tagged Pointers], which are a way to store extra data in the [unused bits of pointers].
67//! While the two differ in implementation, they are semantically the same. In this crate, the [`TaggedBox`] type allows you to store
68//! anywhere from 7 to 16 bits of arbitrary data in your pointer, depending on the [features] enabled. For explanation's sake,
69//! I'll be using the `48bits` feature to explain, as it's the default and leads to the cleanest examples.  
70//! The pointers this applies to are 64 bits long, looking something like this
71//!
72//! ```text
73//! 0000 0000 0000 0000
74//! ```
75//!
76//! However, not all of these bits are used for the actual addressing of memory, so most pointers look like this
77//!
78//! ```text
79//! 0000 FFFF FFFF FFFF
80//! ^^^^
81//! Free Data!
82//! ```
83//!
84//! Those first 16 bits are free data, just begging to be used, and that's what [`TaggedPointer`] does. `TaggedPointer` simply
85//! manages the pointer and the data (referred to as a 'discriminant' throughout this crate), making sure you get a pointer when you
86//! need it and data when you need it, and not mixing those two up.  
87//!
88//! [`TaggedBox`] goes one layer higher, storing an [enum discriminant] (Indicated by the type parameter) and directly storing the enum
89//! variant's inner value to the heap. In short, `TaggedBox` is like a `Box` and an enum rolled into one.  
90//!
91//! Ramping the abstraction up one more notch, we have the [`tagged_box!`] macro, which creates a container-type struct and an
92//! associated `TaggedBox`-backed enum that can be seamlessly transferred between.
93//!
94//! ## Cargo Features
95//!
96//! This crate has a few features that change the number of free and reserved bits:
97//!
98//! - `48bits` (On by default): 48 bits of reserved pointer, 16 bits for data
99//! - `49bits`: 49 bits of reserved pointer, 15 bits for data
100//! - `50bits`: 50 bits of reserved pointer, 14 bits for data
101//! - `51bits`: 51 bits of reserved pointer, 13 bits for data
102//! - `52bits`: 52 bits of reserved pointer, 12 bits for data
103//! - `53bits`: 53 bits of reserved pointer, 11 bits for data
104//! - `54bits`: 54 bits of reserved pointer, 10 bits for data
105//! - `55bits`: 55 bits of reserved pointer, 9 bits for data
106//! - `56bits`: 56 bits of reserved pointer, 8 bits for data
107//! - `57bits`: 57 bits of reserved pointer, 7 bits for data
108//!
109//! However, only one of these may be active at a time, otherwise a `compile_error` will be emitted.
110//!
111//! To select a feature, put the following in your `Cargo.toml`
112//!
113//! ```toml
114//! [dependencies.tagged_box]
115//! version = "0.1.0"
116//! default-features = false
117//! features = ["50bits"] # Select your feature here
118//! ```
119//!
120//! [`Box`]: (https://doc.rust-lang.org/std/boxed/struct.Box.html)
121//! [features]: #cargo-features
122//! [NaN-Boxing]: https://wingolog.org/archives/2011/05/18/value-representation-in-javascript-implementations
123//! [Tagged Pointers]: https://en.wikipedia.org/wiki/Tagged_pointer
124//! [unused bits of pointers]: https://en.wikipedia.org/wiki/X86-64#Virtual_address_space_details
125//! [enum discriminant]: https://doc.rust-lang.org/reference/items/enumerations.html
126//! [`TaggedBox`]: crate::TaggedBox
127//! [`TaggedPointer`]: crate::TaggedPointer
128//! [`tagged_box!`]: macro.tagged_box.html
129
130// `no_std` is supported, but the `alloc` crate is required, as
131// tagged values are allocated to the heap.
132extern crate alloc;
133
134// TODO: Much of this crate can be `const fn` once certain features from #57563 come through
135// Needed features from #57563:
136//    - Dereferencing raw pointers via #51911
137//    - Bounds via rust-lang/rfcs#2632
138//    - &mut T references and borrows via #57349
139//    - Control flow via #49146
140//    - Panics via #51999
141//
142// https://github.com/rust-lang/rust/issues/57563
143//
144// TODO: Figure out how to allow `?Sized` within `TaggedBox` and `TaggedPointer` to allow for
145// parity with `Box`
146
147// 32bit arches use all 32 bits of their pointers, so an extra 16bit tag will have to be added.
148// Any benefit from tagging is kinda lost, but it's worth it to allow compatibility for this crate
149// between 32bit and 64bit arches.
150//
151//
152// Possible `TaggedPointer` structure on 32bit arches:
153// ```rust
154// pub struct TaggedPointer {
155//     ptr: usize,
156//     tag: u8,
157// }
158// ```
159//
160// Note: Tagging of the lower bits is also possible due to alignment, but that only allows for 8 variants at best.
161// this is better than nothing though, and should be implemented
162#[cfg(target_pointer_width = "32")]
163compile_error!("Tagged 32 bit pointers are not currently supported");
164
165// Only pointer widths of 64bits and 32bits will be supported, unless 128bits ever becomes mainstream,
166// but we'll cross that bridge when we get to it.
167#[cfg(not(any(target_pointer_width = "64", target_pointer_width = "32")))]
168compile_error!("Only pointer widths of 64 and 32 will be supported");
169
170/// Macro to create a compile error if none or more than one feature are enabled
171macro_rules! generate_compile_error {
172    ($($feature:literal),+) => {
173        generate_compile_error! {
174            @combinations
175            [$( $feature ),+];
176            [];
177            $( $feature ),*
178        }
179    };
180
181    (@combinations [$($reserved:literal),*]; [$($evaled:meta),*]; $feature:literal, $($features:literal),*) => {
182        generate_compile_error! {
183            @combinations
184            [$( $reserved ),*];
185            [$( $evaled ,)* $( all(feature = $feature, feature = $features) ),*];
186            $( $features ),*
187        }
188    };
189
190    (@combinations [$($reserved:literal),*]; [$($evaled:meta),*]; $feature:literal) => {
191        generate_compile_error! {
192            @combinations
193            [$( $reserved ),*];
194            [$( $evaled ),*];
195        }
196    };
197
198    (@combinations [$($reserved:literal),*]; [$($evaled:meta),*];) => {
199        #[cfg(any(all($( feature = $reserved ),+), $( $evaled ),*))]
200        compile_error!("Please choose only one of the reserved bit width features");
201    };
202}
203
204generate_compile_error! {
205    "48bits", "49bits", "50bits",
206    "51bits", "52bits", "53bits",
207    "54bits", "55bits", "56bits",
208    "57bits"
209}
210
211/// Implement various formatting traits on included structs
212macro_rules! impl_fmt {
213    ($ty:ty => $($fmt:ident),+) => {
214        $(
215            impl fmt::$fmt for $ty {
216                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217                    fmt::$fmt::fmt(&self.as_usize(), f)
218                }
219            }
220        )+
221    };
222
223    // Special case because I don't like rewriting stuff
224    (impl[T: TaggableInner] $ty:ty => $($fmt:ident),+) => {
225        $(
226            impl<T: TaggableInner> fmt::$fmt for $ty {
227                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228                    fmt::$fmt::fmt(&self.as_usize(), f)
229                }
230            }
231        )+
232    };
233
234    // General-purpose arm, used as follows:
235    // ```rust
236    // impl_fmt! {
237    //     impl[T: Debug] TaggedBox<T> => LowerHex,
238    //     impl[T] TaggedBox<T> => UpperHex
239    // }
240    // ```
241    ($(impl[$($generic:tt)*] $ty:ty => $fmt:ident),+) => {
242        $(
243            impl<$($generic)*> fmt::$fmt for $ty {
244                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245                    fmt::$fmt::fmt(&self.as_usize(), f)
246                }
247            }
248        )+
249    };
250}
251
252pub mod discriminant;
253pub mod manually_impl_enum;
254mod taggable;
255mod tagged_box;
256#[macro_use]
257mod tagged_box_macro;
258mod tagged_pointer;
259
260pub use crate::tagged_box::TaggedBox;
261#[doc(inline)]
262pub use discriminant::Discriminant;
263pub use taggable::{TaggableContainer, TaggableInner};
264pub use tagged_pointer::TaggedPointer;