Struct slimmer_box::SlimmerBox
source · #[repr(packed(1))]pub struct SlimmerBox<T, SlimmerMetadata = u32>{ /* private fields */ }
Expand description
A packed alternative to Box<T>
whose ‘fat’ pointer is ‘slimmer’.
A normal Box<[T]>
is an owned ‘fat pointer’ that contains both the ‘raw’ pointer to memory
as well as the size (as an usize) of the managed slice.
On 64-bit targets (where sizeof(usize) == sizeof(u64)), this makes a Box<[T]>
take up 16 bytes (128 bits, 2 words).
That’s a shame: It means that if you build an enum that contains a Box<[T]>
,
then it will at least require 24 bytes (196 bits, 3 words) of stack memory.
But it is rather common to work with slices that will never be that large.
For example, what if we store the size in a u32 instead?
Will your slices really contain more than 2ˆ32 (4_294_967_296) elements?
a [u8; 2^32]
takes 4GiB of space.
And since the length is counted in elements, a [u64; 2^32]
takes 32GiB.
So lets slim this ‘fat’ pointer down! By storing the length inside a u32 rather than a u64, a SlimmerBox<[T], u32> only takes up 12 bytes (96 bits, 1.5 words) rather than 16 bytes.
This allows it to be used inside another structure, such as in one or more variants of an enum. The resulting structure will then still only take up 16 bytes.
In situations where you are trying to optimize for memory usage, cache locality, etc, this might make a difference:
Motivating Example
The following ‘small str optimization’ enum still only takes up two words,
just like a normal &str
would:
use slimmer_box::SlimmerBox;
pub enum CompactStr {
Small{buffer: [u8; 14], len: u8}, // <- Or, using the `modular_bitfield` crate, this could even be { buffer: [u8; 15], len: u4} !
Large{ptr: SlimmerBox<str>},
}
impl From<&str> for CompactStr {
fn from(val: &str) -> CompactStr {
if val.len() < 14 {
let len = val.len() as u8;
let mut buffer = [0u8; 14];
buffer[0..val.len()].copy_from_slice(val.as_bytes());
CompactStr::Small{ buffer, len }
} else {
CompactStr::Large{ ptr: SlimmerBox::new(val) }
}
}
}
let compact_str: CompactStr = "hello world".into();
assert_eq!(core::mem::size_of_val(&compact_str), 16);
// An Option<CompactStr> also only takes up two words:
assert_eq!(core::mem::size_of_val(&Some(compact_str)), 16);
(A full version of this example including Debug, Display and Deref traits can be found in this test)
The following immutable AST still only takes up two words. Even Option<AST>
is only two words:
use slimmer_box::SlimmerBox;
pub enum AST {
Bool(bool),
Int(i64),
Float(f64),
Str(SlimmerBox<str>),
Bytes(SlimmerBox<[u8]>),
List(SlimmerBox<[AST]>),
// 2^32 - 7 other variants could be added and the size would still stay the same :-)
}
assert_eq!(core::mem::size_of::<AST>(), 16);
assert_eq!(core::mem::size_of::<Option<AST>>(), 16);
With some care, you could even combine the above two examples together, and still end up with an AST type that takes up just two words!
Different sizes
SlimmerBox<T, u32> is the most common version, and therefore u32 is the default SlimmerMetadata to use. But it is possible to use another variant, if you are sure that your data will be even shorter.
- SlimmerMetadata =
()
is used for sized types. In this case a SlimmerBox will only contain the normal pointer and be exactly 1 word size, just like a normal Box. - SlimmerMetadata = u64 would make SlimmerBox behave exactly like a normal Box on a 64-bit system.
SlimmerMetadata | max DST length¹ | resulting size (32bit) | resulting size (64bit) | Notes |
---|---|---|---|---|
() | - | 4 bytes | 8 bytes | Used for normal sized types. Identical in size to a normal Box<T> in this case. |
u8 | 255 | 5 bytes | 9 bytes | |
u16 | 65535 | 6 bytes | 10 bytes | Identical to Box<DST> on 16-bit systems |
u32 | 4294967295 | 8 bytes (2 words) | 12 bytes | Identical to Box<DST> on 32-bit systems |
u64 | 18446744073709551615 | -² | 16 bytes (2 words) | Identical to Box<DST> on 64-bit systems |
- ¹ Max DST length is in bytes for
str
and in the number of elements for slices.
Niche optimization
Just like a normal Box, sizeof(Option<SlimmerBox<T>>) == sizeof(SlimmerBox<T>)
.
Rkyv
rkyv’s Archive, Serialize and Deserialize have been implemented for SlimmerBox.
The serialized version of a SlimmerBox<T>
is ‘just’ a normal [rkyv::ArchivedBox<[T]>
].
This is a match made in heaven, since rkyv’s relative pointers use only 32 bits for the pointer part as well as the length part.
As such, sizeof(rkyv::Archived<SlimmerBox<T>>) == 8
bytes (!).
(This is assuming rkyv’s feature size_32
is used which is the default.
Changing it to size_64
is rarely useful for the same reason as the rant about lengths above.)
Limitations
You can not use a SlimmerBox to store a trait object.
This is because the metadata of a dyn
pointer is another full-sized pointer. We cannot make that smaller!
no_std
support
SlimmerBox works perfectly fine in no_std
environments, as long as the alloc
crate is available.
(The only thing that is missing in no_std environments are implementations for SlimmerPointee of std::ffi::OsStr
and std::ffi::CStr
, neither of which exists when std
is disabled.)
Usage Examples
(Below examples assume a 64-bit system)
Smaller than a normal Box for dynamically-sized types like slices or strings:
use slimmer_box::SlimmerBox;
let array: [u64; 4] = [1, 2, 3, 4];
let boxed_slice: Box<[u64]> = Box::from(&array[..]);
assert_eq!(core::mem::size_of_val(&boxed_slice), 16);
let slimmer_boxed_slice: SlimmerBox<[u64]> = SlimmerBox::new(&array[..]);
assert_eq!(core::mem::size_of_val(&slimmer_boxed_slice), 12);
Just like normal Box for normal, Sized types:
use slimmer_box::SlimmerBox;
let int = 42;
let boxed_int = Box::new(&int);
assert_eq!(core::mem::size_of_val(&boxed_int), 8);
let slimmer_boxed_int: SlimmerBox<u64, ()> = SlimmerBox::new(&int);
assert_eq!(core::mem::size_of_val(&slimmer_boxed_int), 8);
You can configure how much space you want to use for the length of a dynamically-sized slice or str:
use slimmer_box::SlimmerBox;
let array: [u64; 4] = [1, 2, 3, 4];
// Holds at most 255 elements:
let tiny: SlimmerBox<[u64], u8> = SlimmerBox::new(&array);
assert_eq!(core::mem::size_of_val(&tiny), 9);
// Holds at most 65535 elements or a str of 64kb:
let small: SlimmerBox<[u64], u16> = SlimmerBox::new(&array);
assert_eq!(core::mem::size_of_val(&small), 10);
// Holds at most 4294967295 elements or a str of 4GB:
let medium: SlimmerBox<[u64], u32> = SlimmerBox::new(&array);
assert_eq!(core::mem::size_of_val(&medium), 12);
// Holds at most 18446744073709551615 elements, or a str of 16EiB:
let large: SlimmerBox<[u64], u64> = SlimmerBox::new(&array); // <- Indistinguishable from a normal Box
assert_eq!(core::mem::size_of_val(&large), 16);
You can turn a Box into a SlimmerBox and vice-versa:
use slimmer_box::SlimmerBox;
let message = "hello, world!";
let boxed = Box::new(message);
let slimmer_box = SlimmerBox::from_box(boxed);
let again_boxed = SlimmerBox::into_box(slimmer_box);
Implementations§
source§impl<T, SlimmerMetadata> SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> SlimmerBox<T, SlimmerMetadata>
sourcepub fn new(value: &T) -> Selfwhere
T: CloneUnsized,
pub fn new(value: &T) -> Selfwhere
T: CloneUnsized,
Creates a new SlimmerBox from the given value (which may be a slice, string or other dynamically sized type).
This involves cloning the slice (which will clone all elements one by one)
and as such only works for types whose contents are cloneable.
Otherwise, use from_box
.
Panics if the value’s Metadata is too large to fit in SlimmerMetadata.
sourcepub unsafe fn new_unchecked(value: &T) -> Selfwhere
T: CloneUnsized,
pub unsafe fn new_unchecked(value: &T) -> Selfwhere
T: CloneUnsized,
Variant of new
that skips its size check.
Safety
The caller must ensure that slice
’s length can fit in a u32.
sourcepub fn try_new(
value: &T
) -> Result<Self, PointerMetadataDoesNotFitError<T, SlimmerMetadata>>where
T: CloneUnsized,
pub fn try_new(
value: &T
) -> Result<Self, PointerMetadataDoesNotFitError<T, SlimmerMetadata>>where
T: CloneUnsized,
Variant of new
which will return an error if the slice is too long instead of panicing.
sourcepub fn try_from_box(
boxed: Box<T>
) -> Result<Self, PointerMetadataDoesNotFitError<T, SlimmerMetadata>>
pub fn try_from_box( boxed: Box<T> ) -> Result<Self, PointerMetadataDoesNotFitError<T, SlimmerMetadata>>
Variant of from_box
which will return an error if the value’s metadata is too large instead of panicing.
sourcepub unsafe fn from_raw(target_ptr: *mut T) -> Self
pub unsafe fn from_raw(target_ptr: *mut T) -> Self
Builds a new SlimmerBox from a raw mutable pointer
Panics if the type’s metadata is too large.
Safety
Caller must ensure *T is valid, and non-null
Furthermore, similar caveats apply as with Box::from_raw.
sourcepub unsafe fn try_from_raw(
target_ptr: *mut T
) -> Result<Self, PointerMetadataDoesNotFitError<T, SlimmerMetadata>>
pub unsafe fn try_from_raw( target_ptr: *mut T ) -> Result<Self, PointerMetadataDoesNotFitError<T, SlimmerMetadata>>
Variant of from_raw
which will return an error if the value’s metadata is too large instead of panicing.
Safety
Caller must ensure *T is valid, and non-null
Furthermore, similar caveats apply as with Box::from_raw.
sourcepub fn from_box(boxed: Box<T>) -> Self
pub fn from_box(boxed: Box<T>) -> Self
Turns a Box into a SlimmerBox.
This is a fast constant-time operation that needs no allocation, as it consumes the box.
Panics if the pointer’s metadata is too large to made slimmer.
sourcepub unsafe fn from_box_unchecked(boxed: Box<T>) -> Self
pub unsafe fn from_box_unchecked(boxed: Box<T>) -> Self
Variant of from_box
that will not check whether the slice is short enough.
Safety
The caller needs to ensure that the conversion from Metadata to SlimmerMetadata will not fail.
sourcepub fn into_box(this: Self) -> Box<T>
pub fn into_box(this: Self) -> Box<T>
Turns a SlimmerBox into a box.
This is a fast constant-time operation that needs no allocation.
Not an associated function to not interfere with Deref, so use fully qualified syntax to call it.
sourcepub fn to_ptr(this: &Self) -> *const T
pub fn to_ptr(this: &Self) -> *const T
Obtains a raw read-only (non-owned) pointer view of the contents of this SlimmerBox.
The resulting pointer is guaranteed to be a valid instance of T and non-null.
This function is mainly useful if you need to implement something that exists for Box but not (yet) for SlimmerBox. Feel free to open an issue or contribute a PR!
Not an associated function to not interfere with Deref, so use fully qualified syntax to call it.
sourcepub fn into_raw(this: Self) -> *mut T
pub fn into_raw(this: Self) -> *mut T
Turns the SlimmerBox into a raw pointer
The resulting pointer is guaranteed to be a valid instance of T and non-null.
Calling this function is safe, but most operations on the result are not. Similar caveats apply as to Box::into_raw.
Not an associated function to not interfere with Deref, so use fully qualified syntax to call it.
sourcepub fn slim_metadata(this: &Self) -> SlimmerMetadata
pub fn slim_metadata(this: &Self) -> SlimmerMetadata
Retrieve access to the stored slimmer metadata value.
Not an associated function to not interfere with Deref, so use fully qualified syntax to call it.
Trait Implementations§
source§impl<T, SlimmerMetadata> Archive for SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> Archive for SlimmerBox<T, SlimmerMetadata>
SlimmerBox is archived into an ArchivedBox<T>
, just like a normal box.
§type Archived = ArchivedBox<<T as ArchiveUnsized>::Archived>
type Archived = ArchivedBox<<T as ArchiveUnsized>::Archived>
§type Resolver = SlimmerBoxResolver<T>
type Resolver = SlimmerBoxResolver<T>
source§impl<T, SlimmerMetadata> AsMut<T> for SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> AsMut<T> for SlimmerBox<T, SlimmerMetadata>
source§impl<T, SlimmerMetadata> AsRef<T> for SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> AsRef<T> for SlimmerBox<T, SlimmerMetadata>
source§impl<T, SlimmerMetadata> Borrow<T> for SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> Borrow<T> for SlimmerBox<T, SlimmerMetadata>
source§impl<T, SlimmerMetadata> BorrowMut<T> for SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> BorrowMut<T> for SlimmerBox<T, SlimmerMetadata>
source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
source§impl<T, SlimmerMetadata> Clone for SlimmerBox<T, SlimmerMetadata>where
T: CloneUnsized + ?Sized + SlimmerPointee<SlimmerMetadata>,
SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,
impl<T, SlimmerMetadata> Clone for SlimmerBox<T, SlimmerMetadata>where
T: CloneUnsized + ?Sized + SlimmerPointee<SlimmerMetadata>,
SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,
source§impl<T, SlimmerMetadata> Debug for SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> Debug for SlimmerBox<T, SlimmerMetadata>
source§impl<T, SlimmerMetadata> Deref for SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> Deref for SlimmerBox<T, SlimmerMetadata>
source§impl<T, SlimmerMetadata> DerefMut for SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> DerefMut for SlimmerBox<T, SlimmerMetadata>
source§impl<'de, T, SlimmerMetadata> Deserialize<'de> for SlimmerBox<[T], SlimmerMetadata>
impl<'de, T, SlimmerMetadata> Deserialize<'de> for SlimmerBox<[T], SlimmerMetadata>
source§fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where
D: Deserializer<'de>,
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where
D: Deserializer<'de>,
source§impl<'de, T, SlimmerMetadata> Deserialize<'de> for SlimmerBox<T, SlimmerMetadata>
impl<'de, T, SlimmerMetadata> Deserialize<'de> for SlimmerBox<T, SlimmerMetadata>
source§fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where
D: Deserializer<'de>,
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where
D: Deserializer<'de>,
source§impl<'de, SlimmerMetadata> Deserialize<'de> for SlimmerBox<str, SlimmerMetadata>
impl<'de, SlimmerMetadata> Deserialize<'de> for SlimmerBox<str, SlimmerMetadata>
source§fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where
D: Deserializer<'de>,
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where
D: Deserializer<'de>,
source§impl<T, D, SlimmerMetadata> Deserialize<SlimmerBox<T, SlimmerMetadata>, D> for ArchivedBox<<T as ArchiveUnsized>::Archived>where
T: ?Sized + SlimmerPointee<SlimmerMetadata> + ArchiveUnsized,
SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,
<T as ArchiveUnsized>::Archived: DeserializeUnsized<T, D>,
D: Fallible + ?Sized,
impl<T, D, SlimmerMetadata> Deserialize<SlimmerBox<T, SlimmerMetadata>, D> for ArchivedBox<<T as ArchiveUnsized>::Archived>where
T: ?Sized + SlimmerPointee<SlimmerMetadata> + ArchiveUnsized,
SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,
<T as ArchiveUnsized>::Archived: DeserializeUnsized<T, D>,
D: Fallible + ?Sized,
source§fn deserialize(
&self,
deserializer: &mut D
) -> Result<SlimmerBox<T, SlimmerMetadata>, D::Error>
fn deserialize( &self, deserializer: &mut D ) -> Result<SlimmerBox<T, SlimmerMetadata>, D::Error>
source§impl<T, SlimmerMetadata> Drop for SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> Drop for SlimmerBox<T, SlimmerMetadata>
source§impl<T, SlimmerMetadata> Hash for SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> Hash for SlimmerBox<T, SlimmerMetadata>
source§impl<T, SlimmerMetadata> Ord for SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> Ord for SlimmerBox<T, SlimmerMetadata>
source§impl<T, SlimmerMetadata> PartialEq for SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> PartialEq for SlimmerBox<T, SlimmerMetadata>
source§impl<T, SlimmerMetadata> PartialOrd for SlimmerBox<T, SlimmerMetadata>where
T: ?Sized + SlimmerPointee<SlimmerMetadata> + PartialOrd,
SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,
impl<T, SlimmerMetadata> PartialOrd for SlimmerBox<T, SlimmerMetadata>where
T: ?Sized + SlimmerPointee<SlimmerMetadata> + PartialOrd,
SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,
source§fn le(&self, other: &Self) -> bool
fn le(&self, other: &Self) -> bool
self
and other
) and is used by the <=
operator. Read moresource§impl<S: Fallible + ?Sized, T, SlimmerMetadata> Serialize<S> for SlimmerBox<T, SlimmerMetadata>
impl<S: Fallible + ?Sized, T, SlimmerMetadata> Serialize<S> for SlimmerBox<T, SlimmerMetadata>
source§impl<T, SlimmerMetadata> Serialize for SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> Serialize for SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> Eq for SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> Send for SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> Sync for SlimmerBox<T, SlimmerMetadata>
impl<T, SlimmerMetadata> Unpin for SlimmerBox<T, SlimmerMetadata>
Auto Trait Implementations§
impl<T: ?Sized, SlimmerMetadata> RefUnwindSafe for SlimmerBox<T, SlimmerMetadata>where
SlimmerMetadata: RefUnwindSafe,
T: RefUnwindSafe,
impl<T: ?Sized, SlimmerMetadata> UnwindSafe for SlimmerBox<T, SlimmerMetadata>where
SlimmerMetadata: UnwindSafe,
T: UnwindSafe,
Blanket Implementations§
source§impl<T> ArchivePointee for T
impl<T> ArchivePointee for T
§type ArchivedMetadata = ()
type ArchivedMetadata = ()
source§fn pointer_metadata(
_: &<T as ArchivePointee>::ArchivedMetadata
) -> <T as Pointee>::Metadata
fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata ) -> <T as Pointee>::Metadata
source§impl<T> ArchiveUnsized for Twhere
T: Archive,
impl<T> ArchiveUnsized for Twhere
T: Archive,
§type Archived = <T as Archive>::Archived
type Archived = <T as Archive>::Archived
Archive
, it may be unsized. Read more