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;