From 987f867c70cb006f0b5602756e97b31a23371138 Mon Sep 17 00:00:00 2001 From: Christofer Nolander Date: Tue, 19 Oct 2021 21:03:19 +0200 Subject: [PATCH] Added some documentation --- Cargo.toml | 2 +- README.md | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/derive.rs | 15 +++++++++ 4 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 README.md diff --git a/Cargo.toml b/Cargo.toml index 1b72619..e9a11fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,4 @@ default = ["derive"] derive = ["dyn_struct_derive"] [dependencies] -dyn_struct_derive = { path = "derive", optional = true } +dyn_struct_derive = { version = "0.1.0", path = "derive", optional = true } diff --git a/README.md b/README.md new file mode 100644 index 0000000..35e9b46 --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ + +# `dyn_struct` + +This crate allows you to safely initialize Dynamically Sized Types (DST) using +only safe Rust. + +```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::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 `&`): + +```rust +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 + +```rust +#[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: + +```rust +impl MyDynamicType { + pub fn new(awesome: bool, number: u32, dynamic: &[u32]) -> Box { + // ... 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(...))`. diff --git a/src/lib.rs b/src/lib.rs index 99f8104..0e93260 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,86 @@ +//! This crate allows you to safely initialize Dynamically Sized Types (DST) using +//! only safe Rust. +//! +//! ```ignore +//! #[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::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 `&`): +//! +//! ```ignore +//! 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 +//! +//! ```ignore +//! #[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: +//! +//! ```ignore +//! impl MyDynamicType { +//! pub fn new(awesome: bool, number: u32, dynamic: &[u32]) -> Box { +//! // ... 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(...))`. + + #[cfg(feature = "derive")] pub use dyn_struct_derive::DynStruct; diff --git a/tests/derive.rs b/tests/derive.rs index 6784413..17e6c31 100644 --- a/tests/derive.rs +++ b/tests/derive.rs @@ -36,3 +36,18 @@ fn generic() { assert_eq!(&foo.values, values); } +#[test] +fn readme() { + #[repr(C)] + #[derive(DynStruct)] + struct MyDynamicType { + pub awesome: bool, + pub number: u32, + pub dynamic: [u32], + } + + let foo: Box = 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]); +}