Struct slimmer_box::SlimmerBox

source ·
#[repr(packed)]
pub struct SlimmerBox<T, SlimmerMetadata = u32>where T: ?Sized + SlimmerPointee<SlimmerMetadata>, SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,{ /* 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.

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.
SlimmerMetadatamax DST length¹resulting size (32bit)resulting size (64bit)Notes
()-4 bytes8 bytesUsed for normal sized types. Identical in size to a normal Box<T> in this case.
u82555 bytes9 bytes
u16655356 bytes10 bytesIdentical to Box<DST> on 16-bit systems
u3242949672958 bytes (2 words)12 bytesIdentical to Box<DST> on 32-bit systems
u641844674407370955161516 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.)

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>where T: ?Sized + SlimmerPointee<SlimmerMetadata>, SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,

source

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.

source

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.

source

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.

source

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.

source

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.

source

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.

source

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.

source

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.

source

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.

source

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.

source

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.

source

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.

source

pub fn metadata(this: &Self) -> <T as Pointee>::Metadata

Returns the outcome of converting the stored SlimmerMetadata value back into its original Metadata form.

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>where T: ?Sized + SlimmerPointee<SlimmerMetadata> + ArchiveUnsized, SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy, Box<T>: Archive,

SlimmerBox is archived into an ArchivedBox<T>, just like a normal box.

§

type Archived = ArchivedBox<<T as ArchiveUnsized>::Archived>

The archived representation of this type. Read more
§

type Resolver = SlimmerBoxResolver<T>

The resolver for this type. It must contain all the additional information from serializing needed to make the archived type from the normal type.
source§

unsafe fn resolve( &self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived )

Creates the archived version of this value at the given position and writes it to the given output. Read more
source§

impl<T, SlimmerMetadata> AsMut<T> for SlimmerBox<T, SlimmerMetadata>where T: ?Sized + SlimmerPointee<SlimmerMetadata>, SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,

source§

fn as_mut(&mut self) -> &mut T

Converts this type into a mutable reference of the (usually inferred) input type.
source§

impl<T, SlimmerMetadata> AsRef<T> for SlimmerBox<T, SlimmerMetadata>where T: ?Sized + SlimmerPointee<SlimmerMetadata>, SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,

source§

fn as_ref(&self) -> &T

Converts this type into a shared reference of the (usually inferred) input type.
source§

impl<T, SlimmerMetadata> Borrow<T> for SlimmerBox<T, SlimmerMetadata>where T: ?Sized + SlimmerPointee<SlimmerMetadata>, SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T, SlimmerMetadata> BorrowMut<T> for SlimmerBox<T, SlimmerMetadata>where T: ?Sized + SlimmerPointee<SlimmerMetadata>, SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T, SlimmerMetadata> Clone for SlimmerBox<T, SlimmerMetadata>where T: Clone + ?Sized + SlimmerPointee<SlimmerMetadata>, SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,

source§

fn clone(&self) -> Self

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl<T, SlimmerMetadata> Debug for SlimmerBox<T, SlimmerMetadata>where T: Debug + ?Sized + SlimmerPointee<SlimmerMetadata>, SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<T, SlimmerMetadata> Deref for SlimmerBox<T, SlimmerMetadata>where T: ?Sized + SlimmerPointee<SlimmerMetadata>, SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,

§

type Target = T

The resulting type after dereferencing.
source§

fn deref(&self) -> &Self::Target

Dereferences the value.
source§

impl<T, SlimmerMetadata> DerefMut for SlimmerBox<T, SlimmerMetadata>where T: ?Sized + SlimmerPointee<SlimmerMetadata>, SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,

source§

fn deref_mut(&mut self) -> &mut Self::Target

Mutably dereferences the value.
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,

source§

fn deserialize( &self, deserializer: &mut D ) -> Result<SlimmerBox<T, SlimmerMetadata>, D::Error>

Deserializes using the given deserializer
source§

impl<T, SlimmerMetadata> Drop for SlimmerBox<T, SlimmerMetadata>where T: ?Sized + SlimmerPointee<SlimmerMetadata>, SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,

source§

fn drop(&mut self)

Executes the destructor for this type. Read more
source§

impl<S: Fallible + ?Sized, T, SlimmerMetadata> Serialize<S> for SlimmerBox<T, SlimmerMetadata>where T: ?Sized + SlimmerPointee<SlimmerMetadata> + SerializeUnsized<S>, SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy, Box<T>: Serialize<S>,

source§

fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error>

Writes the dependencies for the object and returns a resolver that can create the archived type.
source§

impl<T, SlimmerMetadata> Unpin for SlimmerBox<T, SlimmerMetadata>where T: ?Sized + SlimmerPointee<SlimmerMetadata>, SlimmerMetadata: TryFrom<<T as Pointee>::Metadata> + TryInto<<T as Pointee>::Metadata> + Copy,

Auto Trait Implementations§

§

impl<T: ?Sized, SlimmerMetadata> RefUnwindSafe for SlimmerBox<T, SlimmerMetadata>where SlimmerMetadata: RefUnwindSafe, T: RefUnwindSafe,

§

impl<T, SlimmerMetadata = u32> !Send for SlimmerBox<T, SlimmerMetadata>

§

impl<T, SlimmerMetadata = u32> !Sync for SlimmerBox<T, SlimmerMetadata>

§

impl<T: ?Sized, SlimmerMetadata> UnwindSafe for SlimmerBox<T, SlimmerMetadata>where SlimmerMetadata: UnwindSafe, T: UnwindSafe,

Blanket Implementations§

source§

impl<T> Any for Twhere T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> ArchivePointee for T

§

type ArchivedMetadata = ()

The archived version of the pointer metadata for this type.
source§

fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata ) -> <T as Pointee>::Metadata

Converts some archived metadata to the pointer metadata for itself.
source§

impl<T> ArchiveUnsized for Twhere T: Archive,

§

type Archived = <T as Archive>::Archived

The archived counterpart of this type. Unlike Archive, it may be unsized. Read more
§

type MetadataResolver = ()

The resolver for the metadata of this type. Read more
source§

unsafe fn resolve_metadata( &self, _: usize, _: <T as ArchiveUnsized>::MetadataResolver, _: *mut <<T as ArchiveUnsized>::Archived as ArchivePointee>::ArchivedMetadata )

Creates the archived version of the metadata for this value at the given position and writes it to the given output. Read more
source§

unsafe fn resolve_unsized( &self, from: usize, to: usize, resolver: Self::MetadataResolver, out: *mut RelPtr<Self::Archived, <isize as Archive>::Archived> )

Resolves a relative pointer to this value with the given from and to and writes it to the given output. Read more
source§

impl<T> Borrow<T> for Twhere T: ?Sized,

const: unstable · source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere T: ?Sized,

const: unstable · source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> CloneUnsized for Twhere T: Clone,

source§

fn unsized_clone_from(&mut self, source: &T)

Mutates self to become a clone of source. Read more
source§

impl<F, W, T, D> Deserialize<With<T, W>, D> for Fwhere W: DeserializeWith<F, T, D>, D: Fallible + ?Sized, F: ?Sized,

source§

fn deserialize( &self, deserializer: &mut D ) -> Result<With<T, W>, <D as Fallible>::Error>

Deserializes using the given deserializer
source§

impl<T> From<T> for T

const: unstable · source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for Twhere U: From<T>,

const: unstable · source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> Pointee for T

§

type Metadata = ()

The type for metadata in pointers and references to Self.
source§

impl<T> Pointee for T

§

type Metadata = ()

The type for metadata in pointers and references to Self.
source§

impl<T, S> SerializeUnsized<S> for Twhere T: Serialize<S>, S: Serializer + ?Sized,

source§

fn serialize_unsized( &self, serializer: &mut S ) -> Result<usize, <S as Fallible>::Error>

Writes the object and returns the position of the archived type.
source§

fn serialize_metadata(&self, _: &mut S) -> Result<(), <S as Fallible>::Error>

Serializes the metadata for the given type.
source§

impl<T> ToOwned for Twhere T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
const: unstable · source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
const: unstable · source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> SlimmerPointee<()> for T