Skip to content

Commit

Permalink
Added some documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
nolanderc committed Oct 19, 2021
1 parent d86c606 commit 987f867
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
84 changes: 84 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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> = 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<MyDynamicType> {
// ... 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(...))`.
83 changes: 83 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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> = 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<MyDynamicType> {
//! // ... 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;

Expand Down
15 changes: 15 additions & 0 deletions tests/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> = 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]);
}

0 comments on commit 987f867

Please sign in to comment.