├── .gitignore ├── Cargo.toml ├── derive ├── Cargo.toml ├── Cargo.lock └── src │ └── lib.rs ├── LICENSE ├── Cargo.lock ├── tests └── derive.rs ├── README.md └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dyn_struct" 3 | version = "0.3.2" 4 | edition = "2018" 5 | authors = ["Christofer Nolander "] 6 | repository = "https://github.com/nolanderc/dyn_struct" 7 | description = "Construct dynamically sized types safely" 8 | license = "MIT OR Apache-2.0" 9 | 10 | [features] 11 | default = ["derive"] 12 | derive = ["dyn_struct_derive"] 13 | 14 | [dependencies] 15 | dyn_struct_derive = { version = "0.3.0", path = "derive", optional = true } 16 | -------------------------------------------------------------------------------- /derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dyn_struct_derive" 3 | version = "0.3.0" 4 | edition = "2018" 5 | authors = ["Christofer Nolander "] 6 | repository = "https://github.com/nolanderc/dyn_struct" 7 | description = "Derive macros for the `dyn_struct` crate" 8 | license = "MIT OR Apache-2.0" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [lib] 13 | name = "dyn_struct_derive" 14 | path="src/lib.rs" 15 | proc-macro=true 16 | 17 | [dependencies] 18 | proc-macro2 = "1.0.30" 19 | quote = "1.0.10" 20 | syn = { version = "1.0.80", features = ["extra-traits"] } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Christofer Nolander 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /derive/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "dyn_struct_derive" 7 | version = "0.2.0" 8 | dependencies = [ 9 | "proc-macro2", 10 | "quote", 11 | "syn", 12 | ] 13 | 14 | [[package]] 15 | name = "proc-macro2" 16 | version = "1.0.30" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70" 19 | dependencies = [ 20 | "unicode-xid", 21 | ] 22 | 23 | [[package]] 24 | name = "quote" 25 | version = "1.0.10" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 28 | dependencies = [ 29 | "proc-macro2", 30 | ] 31 | 32 | [[package]] 33 | name = "syn" 34 | version = "1.0.80" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" 37 | dependencies = [ 38 | "proc-macro2", 39 | "quote", 40 | "unicode-xid", 41 | ] 42 | 43 | [[package]] 44 | name = "unicode-xid" 45 | version = "0.2.2" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 48 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "dyn_struct" 7 | version = "0.3.2" 8 | dependencies = [ 9 | "dyn_struct_derive", 10 | ] 11 | 12 | [[package]] 13 | name = "dyn_struct_derive" 14 | version = "0.3.0" 15 | dependencies = [ 16 | "proc-macro2", 17 | "quote", 18 | "syn", 19 | ] 20 | 21 | [[package]] 22 | name = "proc-macro2" 23 | version = "1.0.43" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" 26 | dependencies = [ 27 | "unicode-ident", 28 | ] 29 | 30 | [[package]] 31 | name = "quote" 32 | version = "1.0.21" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 35 | dependencies = [ 36 | "proc-macro2", 37 | ] 38 | 39 | [[package]] 40 | name = "syn" 41 | version = "1.0.99" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" 44 | dependencies = [ 45 | "proc-macro2", 46 | "quote", 47 | "unicode-ident", 48 | ] 49 | 50 | [[package]] 51 | name = "unicode-ident" 52 | version = "1.0.3" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" 55 | -------------------------------------------------------------------------------- /tests/derive.rs: -------------------------------------------------------------------------------- 1 | use dyn_struct::DynStruct; 2 | 3 | #[test] 4 | fn custom() { 5 | #[repr(C)] 6 | #[derive(Debug, DynStruct)] 7 | struct Foo { 8 | pub inner: u32, 9 | pub values: [u32], 10 | } 11 | 12 | let foo = Foo::new(14, vec![1, 2, 3, 4]); 13 | assert_eq!(foo.inner, 14); 14 | assert_eq!(&foo.values, [1, 2, 3, 4]); 15 | } 16 | 17 | #[test] 18 | fn generic() { 19 | #[repr(C)] 20 | #[derive(Debug, DynStruct)] 21 | struct Foo<'a, T: Copy, U: Copy> { 22 | pub inner: T, 23 | pub text: &'a str, 24 | pub values: [U], 25 | } 26 | 27 | let foo = Foo::new(true, "hello", [1, 2, 3, 4]); 28 | assert_eq!(foo.inner, true); 29 | assert_eq!(foo.text, "hello"); 30 | assert_eq!(&foo.values, [1, 2, 3, 4]); 31 | } 32 | 33 | #[test] 34 | fn readme() { 35 | #[repr(C)] 36 | #[derive(DynStruct)] 37 | struct MyDynamicType { 38 | pub awesome: bool, 39 | pub number: u32, 40 | pub dynamic: [u32], 41 | } 42 | 43 | let foo: Box = MyDynamicType::new(true, 123, 4..8); 44 | assert_eq!(foo.awesome, true); 45 | assert_eq!(foo.number, 123); 46 | assert_eq!(&foo.dynamic, &[4, 5, 6, 7]); 47 | } 48 | 49 | #[test] 50 | fn non_copy_with_drop() { 51 | use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; 52 | 53 | static MAY_DROP: AtomicBool = AtomicBool::new(false); 54 | static DROP_COUNT: AtomicUsize = AtomicUsize::new(0); 55 | 56 | struct Droppable; 57 | impl Drop for Droppable { 58 | fn drop(&mut self) { 59 | eprintln!("drop"); 60 | let count = DROP_COUNT.fetch_add(1, Ordering::SeqCst); 61 | 62 | // if we are the first drop, but dropping shouldn't happen yet, panic 63 | if count == 0 && !MAY_DROP.load(Ordering::SeqCst) { 64 | panic!("early drop"); 65 | } 66 | } 67 | } 68 | 69 | #[repr(C)] 70 | #[derive(DynStruct)] 71 | struct MyDynamicType { 72 | pub droppable: Droppable, 73 | pub dynamic: [Droppable], 74 | } 75 | 76 | let foo: Box = MyDynamicType::new(Droppable, [Droppable, Droppable, Droppable]); 77 | assert_eq!( 78 | DROP_COUNT.load(Ordering::SeqCst), 79 | 0, 80 | "creating DynStruct should not result in drop" 81 | ); 82 | 83 | MAY_DROP.store(true, Ordering::SeqCst); 84 | drop(foo); 85 | assert_eq!( 86 | DROP_COUNT.load(Ordering::SeqCst), 87 | 4, 88 | "dropping DynStruct should result in drop" 89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Crates.io](https://img.shields.io/crates/v/dyn_struct)](https://crates.io/crates/dyn_struct) 2 | [![docs.rs](https://img.shields.io/docsrs/dyn_struct)](https://docs.rs/dyn_struct) 3 | 4 | # `dyn_struct` 5 | 6 | This crate allows you to safely initialize Dynamically Sized Types (DST) using 7 | only safe Rust. 8 | 9 | ```rust 10 | #[repr(C)] 11 | #[derive(DynStruct)] 12 | struct MyDynamicType { 13 | pub awesome: bool, 14 | pub number: u32, 15 | pub dynamic: [u32], 16 | } 17 | 18 | // the `new` function is generated by the `DynStruct` macro. 19 | let foo: Box = MyDynamicType::new(true, 123, [4, 5, 6, 7]); 20 | assert_eq!(foo.awesome, true); 21 | assert_eq!(foo.number, 123); 22 | assert_eq!(&foo.dynamic, &[4, 5, 6, 7]); 23 | ``` 24 | 25 | 26 | ## Why Dynamic Types? 27 | 28 | In Rust, Dynamically Sized Types (DST) are everywhere. Slices (`[T]`) and trait 29 | objects (`dyn Trait`) are the most common ones. However, it is also possible 30 | to define your own! For example, this can be done by letting the last field in a 31 | struct be a dynamically sized array (note the missing `&`): 32 | 33 | ```rust 34 | struct MyDynamicType { 35 | awesome: bool, 36 | number: u32, 37 | dynamic: [u32], 38 | } 39 | ``` 40 | 41 | This tells the Rust compiler that contents of the `dynamic`-array is laid out in 42 | memory right after the other fields. This can be very preferable in some cases, 43 | since remove one level of indirection and increase cache-locality. 44 | 45 | However, there's a catch! Just as with slices, the compiler does not know how 46 | many elements are in `dynamic`. Thus, we need what is called a fat-pointer which 47 | stores both a pointer to the actual data, but also the length of the array 48 | itself. As of releasing this crate, the only safe way to construct a dynamic 49 | type is if we know the size of the array at compile-time. However, for most use 50 | cases, that is not possible. Therefore this crate uses some `unsafe` behind the 51 | scenes to work around the limitations of the language, all wrapped up in a safe 52 | interface. 53 | 54 | 55 | ## The Derive Macro 56 | 57 | The `DynStruct` macro can be applied to any `#[repr(C)]` struct that contains a 58 | dynamically sized array as its last field. Fields only have a single constraint: 59 | they have to implement `Copy`. 60 | 61 | ### Example 62 | 63 | ```rust 64 | #[repr(C)] 65 | #[derive(DynStruct)] 66 | struct MyDynamicType { 67 | pub awesome: bool, 68 | pub number: u32, 69 | pub dynamic: [u32], 70 | } 71 | ``` 72 | 73 | will produce a single `impl`-block with a `new` function. This function accepts all fileds in 74 | the same order they were declared. The last field, however, can be anything implementing 75 | `IntoIterator`: 76 | 77 | ```rust 78 | impl MyDynamicType { 79 | pub fn new(awesome: bool, number: u32, dynamic: I) -> Box 80 | where I: IntoIterator, 81 | I::IntoIter: ExactSizeIterator, 82 | { 83 | // ... implementation details ... 84 | } 85 | } 86 | ``` 87 | 88 | Due to the nature of dynamically sized types, the resulting value has to be 89 | built on the heap. For safety reasons we currently only allow returning `Box`, 90 | though in a future version we may also allow `Rc` and `Arc`. In the meantime it 91 | is posible to use `Arc::from(MyDynamicType::new(...))`. 92 | -------------------------------------------------------------------------------- /derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | #[proc_macro_derive(DynStruct)] 5 | pub fn derive_dyn_struct(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 6 | let input = syn::parse_macro_input!(input as syn::DeriveInput); 7 | 8 | let output = match expand(input) { 9 | Ok(output) => output, 10 | Err(e) => e.to_compile_error(), 11 | }; 12 | 13 | output.into() 14 | } 15 | 16 | macro_rules! err { 17 | ($span:expr, $($fmt:tt)+) => { 18 | syn::Error::new_spanned($span, format_args!($($fmt)+)) 19 | } 20 | } 21 | 22 | fn expand(input: syn::DeriveInput) -> syn::Result { 23 | match &input.data { 24 | syn::Data::Struct(struc) => { 25 | check_repr(&input)?; 26 | 27 | let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl(); 28 | 29 | let (sized_fields, dynamic_field) = collect_fields(struc)?; 30 | 31 | let single = syn::Ident::new( 32 | &format!("{}_DynStruct_Single", input.ident), 33 | input.ident.span(), 34 | ); 35 | 36 | let phantom_field; 37 | let phantom_init; 38 | if input.generics.lt_token.is_some() { 39 | let variables = input.generics.params.iter().map(|param| match param { 40 | syn::GenericParam::Type(ty) => { 41 | let ident = &ty.ident; 42 | quote! { #ident } 43 | } 44 | syn::GenericParam::Lifetime(life) => { 45 | let lifetime = &life.lifetime; 46 | quote! { &#lifetime () } 47 | } 48 | syn::GenericParam::Const(constant) => { 49 | let ident = &constant.ident; 50 | quote! { [(); #ident] } 51 | } 52 | }); 53 | 54 | phantom_field = quote! { 55 | __DynStruct_phantom: std::marker::PhantomData<(#(#variables,)*)> 56 | }; 57 | phantom_init = quote! { __DynStruct_phantom: std::marker::PhantomData }; 58 | } else { 59 | phantom_field = quote! {}; 60 | phantom_init = quote! {}; 61 | }; 62 | 63 | let single_definition; 64 | let single_init; 65 | let single_idents: Vec; 66 | if matches!(struc.fields, syn::Fields::Named(_)) { 67 | single_definition = quote! { 68 | #[repr(C)] 69 | struct #single #impl_generics #where_clause { 70 | #(#sized_fields,)* 71 | #phantom_field 72 | } 73 | }; 74 | single_idents = sized_fields 75 | .iter() 76 | .map(|field| field.ident.clone().unwrap()) 77 | .collect(); 78 | single_init = quote! { #single { #(#single_idents,)* #phantom_init } }; 79 | } else { 80 | single_definition = quote! { 81 | #[repr(C)] 82 | struct #single #impl_generics ( #(#sized_fields,)* #phantom_init ) #where_clause; 83 | }; 84 | single_idents = sized_fields 85 | .iter() 86 | .enumerate() 87 | .map(|(i, field)| syn::Ident::new(&format!("_{}", i), span(field))) 88 | .collect(); 89 | single_init = quote! { #single ( #(#single_idents,)*, #phantom_init ) }; 90 | }; 91 | 92 | let sized_parameters = sized_fields.iter().enumerate().map(|(i, field)| { 93 | let name = &single_idents[i]; 94 | let ty = &field.ty; 95 | quote! { #name: #ty } 96 | }); 97 | 98 | let dynamic_type = match &dynamic_field.ty { 99 | syn::Type::Slice(inner) => inner.elem.as_ref(), 100 | _ => { 101 | return Err(err!( 102 | dynamic_field.ty, 103 | "the last field needs to be a slice `[T]`" 104 | )) 105 | } 106 | }; 107 | let dynamic_name = dynamic_field 108 | .ident 109 | .clone() 110 | .unwrap_or_else(|| syn::Ident::new("tail", span(dynamic_type))); 111 | 112 | let struct_ident = &input.ident; 113 | Ok(quote! { 114 | impl #impl_generics #struct_ident #type_generics #where_clause { 115 | pub fn new(#(#sized_parameters,)* #dynamic_name: I) -> Box 116 | where I: std::iter::IntoIterator, 117 | ::IntoIter: std::iter::ExactSizeIterator 118 | { 119 | #single_definition 120 | 121 | let header: #single #type_generics = #single_init; 122 | 123 | let dyn_struct = dyn_struct::DynStruct::new(header, #dynamic_name); 124 | let ptr = std::boxed::Box::into_raw(dyn_struct); 125 | unsafe { std::boxed::Box::from_raw(ptr as *mut Self) } 126 | } 127 | } 128 | }) 129 | } 130 | _ => Err(err!( 131 | &input.ident, 132 | "`DynStruct` can only be derived for structs" 133 | )), 134 | } 135 | } 136 | 137 | fn span(value: &T) -> proc_macro2::Span { 138 | value.span() 139 | } 140 | 141 | fn collect_fields(struc: &syn::DataStruct) -> syn::Result<(Vec, syn::Field)> { 142 | let mut fields = struc.fields.clone(); 143 | 144 | for field in fields.iter_mut() { 145 | field.attrs.clear(); 146 | field.vis = syn::Visibility::Inherited; 147 | } 148 | 149 | let dynamic = match &mut fields { 150 | syn::Fields::Named(fields) => fields.named.pop(), 151 | syn::Fields::Unnamed(fields) => fields.unnamed.pop(), 152 | syn::Fields::Unit => None, 153 | }; 154 | 155 | let dynamic = 156 | dynamic.ok_or_else(|| err!(&struc.fields, "cannot derive `DynStruct` for empty struct"))?; 157 | 158 | Ok((fields.into_iter().collect(), dynamic.into_value())) 159 | } 160 | 161 | fn check_repr(input: &syn::DeriveInput) -> syn::Result<()> { 162 | if input.attrs.iter().any(|attr| is_repr_c(attr)) { 163 | Ok(()) 164 | } else { 165 | Err(err!( 166 | &input.ident, 167 | "`DynStruct` can only be derived for structs with `#[repr(C)]`" 168 | )) 169 | } 170 | } 171 | 172 | fn is_repr_c(attr: &syn::Attribute) -> bool { 173 | match attr.path.get_ident() { 174 | Some(ident) if ident == "repr" => {} 175 | _ => return false, 176 | } 177 | 178 | match find_ident(attr.tokens.clone()) { 179 | Some(ident) if ident == "C" => true, 180 | _ => false, 181 | } 182 | } 183 | 184 | fn find_ident(tokens: TokenStream) -> Option { 185 | tokens.into_iter().find_map(|tree| match tree { 186 | quote::__private::TokenTree::Group(group) => find_ident(group.stream()), 187 | quote::__private::TokenTree::Ident(ident) => Some(ident.clone()), 188 | _ => None, 189 | }) 190 | } 191 | 192 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate allows you to safely initialize Dynamically Sized Types (DST) using 2 | //! only safe Rust. 3 | //! 4 | //! ```ignore 5 | //! #[repr(C)] 6 | //! #[derive(DynStruct)] 7 | //! struct MyDynamicType { 8 | //! pub awesome: bool, 9 | //! pub number: u32, 10 | //! pub dynamic: [u32], 11 | //! } 12 | //! 13 | //! // the `new` function is generated by the `DynStruct` macro. 14 | //! let foo: Box = MyDynamicType::new(true, 123, [4, 5, 6, 7]); 15 | //! assert_eq!(foo.awesome, true); 16 | //! assert_eq!(foo.number, 123); 17 | //! assert_eq!(&foo.dynamic, &[4, 5, 6, 7]); 18 | //! ``` 19 | //! 20 | //! 21 | //! ## Why Dynamic Types? 22 | //! 23 | //! In Rust, Dynamically Sized Types (DST) are everywhere. Slices (`[T]`) and trait 24 | //! objects (`dyn Trait`) are the most common ones. However, it is also possible 25 | //! to define your own! For example, this can be done by letting the last field in a 26 | //! struct be a dynamically sized array (note the missing `&`): 27 | //! 28 | //! ```ignore 29 | //! struct MyDynamicType { 30 | //! awesome: bool, 31 | //! number: u32, 32 | //! dynamic: [u32], 33 | //! } 34 | //! ``` 35 | //! 36 | //! This tells the Rust compiler that contents of the `dynamic`-array is laid out in 37 | //! memory right after the other fields. This can be very preferable in some cases, 38 | //! since remove one level of indirection and increase cache-locality. 39 | //! 40 | //! However, there's a catch! Just as with slices, the compiler does not know how 41 | //! many elements are in `dynamic`. Thus, we need what is called a fat-pointer which 42 | //! stores both a pointer to the actual data, but also the length of the array 43 | //! itself. As of releasing this crate, the only safe way to construct a dynamic 44 | //! type is if we know the size of the array at compile-time. However, for most use 45 | //! cases, that is not possible. Therefore this crate uses some `unsafe` behind the 46 | //! scenes to work around the limitations of the language, all wrapped up in a safe 47 | //! interface. 48 | //! 49 | //! 50 | //! ## The Derive Macro 51 | //! 52 | //! The `DynStruct` macro can be applied to any `#[repr(C)]` struct that contains a 53 | //! dynamically sized array as its last field. Fields only have a single constraint: 54 | //! they have to implement `Copy`. 55 | //! 56 | //! ### Example 57 | //! 58 | //! ```ignore 59 | //! #[repr(C)] 60 | //! #[derive(DynStruct)] 61 | //! struct MyDynamicType { 62 | //! pub awesome: bool, 63 | //! pub number: u32, 64 | //! pub dynamic: [u32], 65 | //! } 66 | //! ``` 67 | //! 68 | //! will produce a single `impl`-block with a `new` function. This function accepts all fileds in 69 | //! the same order they were declared. The last field, however, can be anything implementing 70 | //! `IntoIterator`: 71 | //! 72 | //! ```ignore 73 | //! impl MyDynamicType { 74 | //! pub fn new(awesome: bool, number: u32, dynamic: I) -> Box 75 | //! where I: IntoIterator, 76 | //! I::IntoIter: ExactSizeIterator, 77 | //! { 78 | //! // ... implementation details ... 79 | //! } 80 | //! } 81 | //! ``` 82 | //! 83 | //! Due to the nature of dynamically sized types, the resulting value has to be 84 | //! built on the heap. For safety reasons we currently only allow returning `Box`, 85 | //! though in a future version we may also allow `Rc` and `Arc`. In the meantime it 86 | //! is posible to use `Arc::from(MyDynamicType::new(...))`. 87 | 88 | #[cfg(feature = "derive")] 89 | pub use dyn_struct_derive::DynStruct; 90 | 91 | use std::mem::{align_of, size_of, MaybeUninit}; 92 | 93 | #[repr(C)] 94 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 95 | pub struct DynStruct { 96 | pub header: Header, 97 | pub tail: [Tail], 98 | } 99 | 100 | impl DynStruct { 101 | /// Allocate a new `DynStruct` on the heap. Initialized lazily using an iterator. 102 | #[inline] 103 | pub fn new(header: Header, tail: I) -> Box 104 | where 105 | I: IntoIterator, 106 | I::IntoIter: ExactSizeIterator, 107 | { 108 | let tail = tail.into_iter(); 109 | 110 | let mut writer = BoxWriter::::new(header, tail.len()); 111 | 112 | for value in tail { 113 | writer.write_tail::(value); 114 | } 115 | 116 | writer.finish::() 117 | } 118 | 119 | /// Allocate a new `DynStruct` on the heap. Uses a slice instead of an iterator (as 120 | /// [`DynStruct::new`]). This will probably be faster in most cases (provided the slice is 121 | /// readily available). 122 | pub fn from_slice(header: Header, tail: &[Tail]) -> Box 123 | where 124 | Tail: Copy, 125 | { 126 | let mut writer = BoxWriter::::new(header, tail.len()); 127 | unsafe { 128 | writer.write_slice(tail); 129 | } 130 | writer.finish::<()>() 131 | } 132 | 133 | #[inline] 134 | fn align() -> usize { 135 | usize::max(align_of::
(), align_of::()) 136 | } 137 | 138 | /// Returns the total size of the `DynStruct` structure, provided the length of the 139 | /// tail. 140 | #[inline] 141 | fn size(len: usize) -> usize { 142 | let header = size_of::
(); 143 | 144 | let tail_align = align_of::(); 145 | let padding = if header % tail_align == 0 { 146 | 0 147 | } else { 148 | tail_align - header % tail_align 149 | }; 150 | 151 | let tail = size_of::() * len; 152 | 153 | header + padding + tail 154 | } 155 | } 156 | 157 | struct BoxWriter { 158 | raw: *mut DynStruct>, 159 | written: usize, 160 | } 161 | 162 | impl BoxWriter { 163 | #[inline] 164 | pub fn new(header: Header, len: usize) -> Self { 165 | let total_size = DynStruct::::size(len); 166 | let align = DynStruct::::align(); 167 | 168 | let fat_ptr = if total_size == 0 { 169 | // We cannot allocate a region of 0 bytes, thus we use `Box` to create a dangling 170 | // pointer with the correct length. 171 | let slice: Box<[()]> = Box::from(slice_with_len(len)); 172 | 173 | // This is a zero-size-type, so moving it into the allocation has no effect. Instead we 174 | // just create a new one "out of thin air" by casting the allocation to the appropriate 175 | // header. We need this call to `forget` to make sure the destructor of the original 176 | // header does not run. 177 | std::mem::forget(header); 178 | 179 | Box::into_raw(slice) as *mut DynStruct> 180 | } else { 181 | unsafe { 182 | // Allocate enough memory to store both the header and tail 183 | let layout = std::alloc::Layout::from_size_align(total_size, align).unwrap(); 184 | let raw = std::alloc::alloc(layout); 185 | if raw.is_null() { 186 | std::alloc::handle_alloc_error(layout) 187 | } 188 | 189 | // Initialize the header field 190 | raw.cast::
().write(header); 191 | 192 | // use a slice as an intermediary to get a fat pointer containing the correct 193 | // length of the tail 194 | let slice = std::slice::from_raw_parts_mut(raw as *mut (), len); 195 | slice as *mut [()] as *mut DynStruct> 196 | } 197 | }; 198 | 199 | BoxWriter { 200 | raw: fat_ptr, 201 | written: 0, 202 | } 203 | } 204 | 205 | #[inline] 206 | fn write_tail(&mut self, value: Tail) { 207 | let written = self.written; 208 | let tail = &mut self.as_mut().tail; 209 | 210 | assert!( 211 | written < tail.len(), 212 | "got more items than expected. Probable bug in `ExactSizeIterator` for `{}`?", 213 | std::any::type_name::(), 214 | ); 215 | 216 | tail[written].write(value); 217 | self.written += 1; 218 | } 219 | 220 | #[inline] 221 | fn finish(mut self) -> Box> { 222 | let len = self.as_mut().tail.len(); 223 | assert_eq!( 224 | self.written, 225 | len, 226 | "got fewer items than expected. Probable bug in `ExactSizeIterator` for `{}`?", 227 | std::any::type_name::(), 228 | ); 229 | 230 | // This cast from `DynStruct>` is sound since all tail elements 231 | // now have been initialized 232 | let init = self.raw as *mut DynStruct; 233 | 234 | // once we have finished constructing the value, don't run the destructor 235 | std::mem::forget(self); 236 | 237 | unsafe { Box::from_raw(init) } 238 | } 239 | 240 | fn as_mut(&mut self) -> &mut DynStruct> { 241 | unsafe { &mut *self.raw } 242 | } 243 | 244 | /// # Safety 245 | /// 246 | /// Assumes that this writer was created with a capacity for `values.len()` 247 | unsafe fn write_slice(&mut self, values: &[Tail]) 248 | where 249 | Tail: Copy, 250 | { 251 | self.as_mut() 252 | .tail 253 | .as_mut_ptr() 254 | .copy_from_nonoverlapping(values.as_ptr().cast(), values.len()); 255 | self.written = values.len(); 256 | } 257 | } 258 | 259 | impl Drop for BoxWriter { 260 | fn drop(&mut self) { 261 | unsafe { 262 | // SAFETY: the header field is always initialized 263 | std::ptr::drop_in_place(self.raw.cast::
()); 264 | 265 | let initialized = self.written; 266 | let tail = &mut self.as_mut().tail; 267 | for i in 0..initialized { 268 | tail[i].as_mut_ptr().drop_in_place(); 269 | } 270 | } 271 | } 272 | } 273 | 274 | impl DynStruct { 275 | /// Get a `DynStruct` as a view over a slice (this does not allocate). 276 | pub fn slice_view(values: &[T]) -> &Self { 277 | assert!( 278 | !values.is_empty(), 279 | "attempted to create `{}` from empty slice (needs at least 1 element)", 280 | std::any::type_name::() 281 | ); 282 | let slice = &values[..values.len() - 1]; 283 | unsafe { &*(slice as *const [T] as *const Self) } 284 | } 285 | } 286 | 287 | impl DynStruct<[T; N], T> { 288 | /// Get a `DynStruct` as a view over a slice (this does not allocate). 289 | pub fn slice_view(values: &[T]) -> &Self { 290 | assert!( 291 | values.len() >= N, 292 | "attempted to create `{}` from empty slice (needs at least {} elements)", 293 | std::any::type_name::(), 294 | N, 295 | ); 296 | let slice = &values[..values.len() - N]; 297 | unsafe { &*(slice as *const [T] as *const Self) } 298 | } 299 | } 300 | 301 | fn slice_with_len(len: usize) -> &'static [()] { 302 | static ARBITRARY: [(); usize::MAX] = [(); usize::MAX]; 303 | &ARBITRARY[..len] 304 | } 305 | 306 | #[cfg(test)] 307 | mod tests { 308 | use super::*; 309 | 310 | #[test] 311 | fn mixed_types() { 312 | let mixed = DynStruct::new((true, 32u16), [1u64, 2, 3, 4]); 313 | assert_eq!(mixed.header, (true, 32u16)); 314 | assert_eq!(&mixed.tail, &[1, 2, 3, 4]); 315 | } 316 | 317 | #[test] 318 | fn zero_sized_types() { 319 | let zero = DynStruct::new((), [(), ()]); 320 | assert_eq!(zero.header, ()); 321 | assert_eq!(&zero.tail, &[(), ()]); 322 | } 323 | 324 | #[test] 325 | fn from_slice() { 326 | let slice = DynStruct::from_slice((true, 32u16), &[1, 2, 3]); 327 | assert_eq!(slice.header, (true, 32u16)); 328 | assert_eq!(&slice.tail, &[1, 2, 3]); 329 | 330 | let array = DynStruct::<[u32; 3], u32>::slice_view(&[1, 2, 3, 4, 5]); 331 | assert_eq!(array.header, [1, 2, 3]); 332 | assert_eq!(&array.tail, &[4, 5]); 333 | } 334 | 335 | #[test] 336 | fn slice_view() { 337 | let same = DynStruct::::slice_view(&[1, 2, 3]); 338 | assert_eq!(same.header, 1); 339 | assert_eq!(&same.tail, &[2, 3]); 340 | 341 | let array = DynStruct::<[u32; 3], u32>::slice_view(&[1, 2, 3, 4, 5]); 342 | assert_eq!(array.header, [1, 2, 3]); 343 | assert_eq!(&array.tail, &[4, 5]); 344 | } 345 | } 346 | --------------------------------------------------------------------------------