3 releases (breaking)
0.3.2 | Aug 14, 2022 |
---|---|
0.3.1 |
|
0.3.0 |
|
0.2.0 | Oct 20, 2021 |
0.1.0 | Oct 19, 2021 |
#1922 in Rust patterns
16KB
196 lines
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