#traits #trait-object #heap-allocation #memory-block #memory-management #dynamic-dispatch #fixed-size

nightly no-std sized-dst

Owned container for dynamically-sized types backed by inline memory

2 releases

0.1.1 Aug 30, 2024
0.1.0 Aug 11, 2024

#1012 in Rust patterns

MIT/Apache

35KB
614 lines

sized-dst

This crate provides Dst, an owned container for dynamically-sized types (DSTs) that's backed by inline memory. The main use-case is owned trait objects without heap allocation. You can think of it as a stack-only version of Box<dyn Trait>.

This crate currently requires nightly, since it relies on Unsize and pointer metadata.

The layout of sized_dst::Dst consists of the DST metadata (for trait objects, this is the vtable pointer) and a fixed block of memory storing the actual object. This allows it to live entirely on the stack or even a static variable. The size and alignment of its memory block are specified by generic parameters, so a sized_dst::Dst can only store objects that fit in its memory block. If the object does not fulfill the size or alignment requirements, the constructor of sized_dst::Dst will emit a compile-time error.

use sized_dst::{Dst, max_size};

trait Draw {
    fn draw(&self);
}

struct Button {
    height: u32,
    width: u32,
    name: &'static str,
}
impl Draw for Button {
    fn draw(&self) { /* draw the button */ }
}

struct Checkbox {
    on: bool,
}
impl Draw for Checkbox {
    fn draw(&self) { /* draw the checkbox */ }
}

struct Text(&'static str);
impl Draw for Text {
    fn draw(&self) { /* draw the text */ }
}

struct Blob([u8; 100]);
impl Draw for Blob {
    fn draw(&self) { /* draw the blob */ }
}

impl Draw for u32 {
    fn draw(&self) { /* draw the u32 */ }
}

fn main() {
    // Each Dst stores a `dyn Draw` up to a fixed capacity, which is set via `max_size` to
    // the size of the biggest trait object we're using.
    let drawables: &[Dst<dyn Draw, { max_size!(Checkbox, Text, Button) }>] = &[
        Dst::new(Checkbox { on: true }),
        Dst::new(Text("lorem ipsum")),
        Dst::new(Button {
            height: 20,
            width: 20,
            name: "PANIC BUTTON",
        }),
        Dst::new(1u32),   // u32 is smaller than the fixed capacity we specified, so we can use it
        // Dst::new(Blob([0; 100]))    This would not compile, because Blob doesn't fit in Dst
    ];

    // Perform dynamic dispatch over the Draw trait
    for d in drawables {
        d.draw();
    }
}

Compared to Box<dyn Trait>, Dst requires no indirection or allocation, making it more cache-friendly and usable in environments with no allocator. In exchange, Dst can only store objects up to a fixed size, making it less flexible than Box<dyn Trait>. In a way, Dst offers a compromise between enums and boxed trait objects.

Optional Features

  • std: Enable implementations of std traits.

Dependencies

~26KB