#dynamically-sized #dst #dynamic #dyn #pointers #slice #unsized

nightly dyn_struct2

Construct dynamically sized types safely. Supports arbitrary unsized types, not just slices

1 unstable release

0.1.0 Aug 11, 2022

#1917 in Rust patterns


Used in swifer

MIT/Apache

16KB
162 lines

Crates.io docs.rs

dyn_struct2

Modification of dyn_struct (GitHub, crates.io) which supports any unsized type, not just slices.

This crate allows you to initialize Dynamically Sized Types (DST) using only safe Rust. It also provides helpes to creates DSTs in unsafe Rust which handle the "gritty" details of alignment and fat pointers.

// Helpers for gritty details
use dyn_struct2::{DynStruct, dyn_arg};
// Automatic #[derive] proc macro
use dyn_struct_derive2::{DynStruct as DynStructDerive};

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

fn example() {
    let data = [4, 5, 6, 7];

    // the `new` function is generated by the `DynStruct` macro.
    let foo: Box<MyDynamicType> = MyDynamicType::new(true, 123, dyn_arg!(data));
    assert_eq!(foo.awesome, true);
    assert_eq!(foo.number, 123);
    assert_eq!(&foo.dynamic, &[4, 5, 6, 7]);
    
    // You can also create values unsafely to generate your own constructors, or if you don't like proc macros
    let data2 = [7, 8, 9];
    let foo2 = DynStruct::<(bool, u32), [u32]>::new((false, 456), dyn_arg!(data2));
    let foo2 = unsafe { foo2.transmute::<MyDynamicType>() };
    assert_eq!(foo2.awesome, false);
    assert_eq!(foo2.number, 456);
    assert_eq!(&foo2.dynamic, &[7, 8, 9]);
}

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 DST:

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

This tells the Rust compiler that contents of the DST are laid out in memory right after the other fields, and a pointer to MyDynamicType will be a fat pointer with the same metadata as the DST. This can be very preferable in some cases, since it removes one level of indirection and increases cache-locality.

However, there's a catch! Just as with slices, the compiler does not know how the size of dynamic. Thus, we need what is called a fat-pointer which stores both a pointer to the actual data, but also the size of the DST itself (and additional metadata, e.g. if it's a dyn the vtable). Rust's built-in support for DSTs is lacking, and there are no safe ways to construct these DSTs, as you can't actually pass unsized types to a constructor or function parameter. 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.

Example

#![cfg(feature = "derive")]
use dyn_struct2::DynStruct;

#[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 fields in the same order they were declared. The last field, however, must be a DynArg:

# use dyn_struct2::DynArg;
#
# #[repr(C)]
# struct MyDynamicType {
#     pub awesome: bool,
#     pub number: u32,
#     pub dynamic: [u32],
# }

impl MyDynamicType {
    pub fn new(awesome: bool, number: u32, dynamic: DynArg<[u32]>) -> Box<MyDynamicType> {
        // ... implementation details ...
        todo!()
    }
}

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 possible to use Arc::from(MyDynamicType::new(...)).

The DynStruct datatype

DynStruct is a generic type which can take on the shape of any DST which has #[repr(C)]. It consists of a head (which may be a single value or tuple of your statically-sized fields), and a tail (which may be the DST), and is literally just:

#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DynStruct<Header, Tail: ?Sized> {
    pub header: Header,
    pub tail: Tail
}

The key method in DynStruct is DynStruct::transmute, which allows you to marshal this into any dynamically-sized structure. This is an unsafe but easy way to create safe constructors for your own DSTs:

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

impl MyDynamicType {
    pub fn new(awesome: bool, number: u32, dynamic: DynArg<[u32]>) -> Box<MyDynamicType> {
        // This is the actual generated body of MyDynamicType if you use #[derive(DynStruct)]!
        let dyn_struct = DynStruct::new((awesome, number), dynamic);
        unsafe { dyn_struct.transmute() }
    }
}

DynArg

DynArg is a wrapper for dynamically-sized types so that you can consume them and pass them as arguments to a function. You create a DynArg with the macro dyn_arg!(arg).

Once you call dyn_arg!(arg), arg is effectively consumed. However it is not dropped unless you pass the dyn_arg to a DynStruct constructor, and then drop the constructed value. If you just ignore the dyn_arg then arg will leak, which is technically safe behavior but be aware.

Dependencies

~210KB