boxing/nan.rs
1//! Implementations of NaN-boxes, with support for strict-typing and a clean primitive for building
2//! your own.
3//!
4//! ## What is a NaN Box?
5//!
6//! A NaN Box is a form of data storage which takes advantage of the fact that IEEE floats have a
7//! very large number of NaN representations - a full 6 bytes and 3 bits worth of them, in fact.
8//! We can take advantage of that to store smaller data types, including integer or pointer values,
9//! in those valid NaN representations.
10//!
11//! Now you might be saying 'wait, aren't pointers on 64 bit systems also 64 bit?' It's true that
12//! the size of a pointer on 64 bit systems is <u class="mousetext" title="64 bits">8 bytes</u>, but
13//! there's a secret - most systems only actually use the first <u class="mousetext" title="48 bits">6 bytes</u>
14//! of address space. This means that we can store any pointer on most systems - they'll fit in the
15//! <u class="mousetext" title="48 bits">6 bytes</u> storage
16//!
17//! ## Why use a NaN Box?
18//!
19//! NaN-boxing data is a technique most frequently used in interpreters, to save memory - they're
20//! effectively a niche-optimized enum, where the niche is the NaN values of the stored float. This
21//! can save up to 8 bytes of memory for every object, for minimal runtime cast, which adds up
22//! quite fast.
23//!
24//! ## Limitations
25//!
26//! - Since all data must fit in the 6 bytes of space a float provides us, we cannot store
27//!   wide-pointers, so all data stored must be `Sized`. This can be worked around by using
28//!   double-indirection, storing a `Box<T: ?Sized>` or similar.
29//!
30//! ## Examples
31//!
32//! A general example of using [`NanBox`] with some large data type:
33//!
34//! ```
35//! # use boxing::nan::NanBox;
36//! # #[cfg(not(miri))] {
37//!
38//! // The thing we're storing in our box - a data type in our program
39//! #[derive(Debug)]
40//! pub struct InterpValue<'a> {
41//!     class: u64,
42//!     fields: Vec<(String, NanBox<'a, InterpValue<'a>>)>,
43//! }
44//!
45//! // We can make a list of boxes, which takes up the same amount of memory as a `Vec<f64>`
46//! let mut values = vec![
47//!     NanBox::from_float(1.0),
48//!     NanBox::from_inline(4),
49//!     NanBox::from_box(Box::new(
50//!         InterpValue { class: 1, fields: vec![(String::from("foo"), NanBox::from_float(-1.0))] }
51//!     )),
52//! ];
53//!
54//! // We want to evaluate, say, +1 if the value is a float:
55//! for v in &mut values {
56//!     if let Some(f) = v.try_mut_float() {
57//!         // If you look at `try_mut_float`, it actually returns a `&mut SingleNaNF64` - this type
58//!         // protects from writing NaN values that might change the type of the box into not-a-float,
59//!         // but it can be used drop-in like a float in most places.
60//!         *f += 1.0;
61//!     }
62//! }
63//!
64//! // Alternatively, we want to read an object and get its fields:
65//! for v in &values {
66//!     if let Some(obj) = v.try_ref_boxed() {
67//!         println!("{}", obj.fields[0].0);
68//!     }
69//! }
70//!
71//! // Final note: since every type is represented as a float, it's always safe and sound to turn
72//! // one into a float with no branches. This can leak memory though, so be careful.
73//! for v in values {
74//!     // Prints 2.0, nan, and nan. Leaks `InterpValue`.
75//!     println!("{}", v.into_float_unchecked());
76//! }
77//!
78//! # }
79//! ```
80//!
81
82pub mod heap;
83pub mod raw;
84mod singlenan;
85
86pub use heap::NanBox;
87pub use raw::RawBox;
88pub use singlenan::SingleNaNF64;
89
90const SIGN_MASK: u64 = 0x7FFF_FFFF_FFFF_FFFF;
91const QUIET_NAN: u64 = 0x7FF8_0000_0000_0000;
92const NEG_QUIET_NAN: u64 = 0xFFF8_0000_0000_0000;