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