3 releases (breaking)

0.3.2 Aug 14, 2022
0.3.1 Aug 14, 2022
0.3.0 Aug 14, 2022
0.2.0 Oct 20, 2021
0.1.0 Oct 19, 2021

#1922 in Rust patterns

MIT/Apache

16KB
196 lines

Crates.io docs.rs

dyn_struct

This crate allows you to safely initialize Dynamically Sized Types (DST) using only safe Rust.

#[repr(C)]
#[derive(DynStruct)]
struct MyDynamicType {
    pub awesome: bool,
    pub number: u32,
    pub dynamic: [u32],
}

// the `new` function is generated by the `DynStruct` macro.
let foo: Box<MyDynamicType> = MyDynamicType::new(true, 123, [4, 5, 6, 7]);
assert_eq!(foo.awesome, true);
assert_eq!(foo.number, 123);
assert_eq!(&foo.dynamic, &[4, 5, 6, 7]);

Why Dynamic Types?

In Rust, Dynamically Sized Types (DST) are everywhere. Slices ([T]) and trait objects (dyn Trait) are the most common ones. However, it is also possible to define your own! For example, this can be done by letting the last field in a struct be a dynamically sized array (note the missing &):

struct MyDynamicType {
    awesome: bool,
    number: u32,
    dynamic: [u32],
}

This tells the Rust compiler that contents of the dynamic-array is laid out in memory right after the other fields. This can be very preferable in some cases, since remove one level of indirection and increase cache-locality.

However, there's a catch! Just as with slices, the compiler does not know how many elements are in dynamic. Thus, we need what is called a fat-pointer which stores both a pointer to the actual data, but also the length of the array itself. As of releasing this crate, the only safe way to construct a dynamic type is if we know the size of the array at compile-time. However, for most use cases, that is not possible. Therefore this crate uses some unsafe behind the scenes to work around the limitations of the language, all wrapped up in a safe interface.

The Derive Macro

The DynStruct macro can be applied to any #[repr(C)] struct that contains a dynamically sized array as its last field. Fields only have a single constraint: they have to implement Copy.

Example

#[repr(C)]
#[derive(DynStruct)]
struct MyDynamicType {
    pub awesome: bool,
    pub number: u32,
    pub dynamic: [u32],
}

will produce a single impl-block with a new function. This function accepts all fileds in the same order they were declared. The last field, however, can be anything implementing IntoIterator:

impl MyDynamicType {
    pub fn new<I>(awesome: bool, number: u32, dynamic: I) -> Box<MyDynamicType> 
        where I: IntoIterator<Item = u32>,
              I::IntoIter: ExactSizeIterator,
    {
        // ... implementation details ...
    }
}

Due to the nature of dynamically sized types, the resulting value has to be built on the heap. For safety reasons we currently only allow returning Box, though in a future version we may also allow Rc and Arc. In the meantime it is posible to use Arc::from(MyDynamicType::new(...)).

Dependencies

~230KB