dst_factory/
lib.rs

1//! C-like [flexible array members](https://en.wikipedia.org/wiki/Flexible_array_member) for Rust.
2//!
3//! This crate lets you allocate variable data inline at the end of a struct. If you have a
4//! struct that gets allocated on the heap and has some variable-length data associated with it
5//! (like a string or an array), then you can allocate this data directly inline with the struct.
6//! This saves memory by avoiding the need for a pointer and a separate allocation, and saves CPU
7//! cycles by eliminating the need for indirection when accessing the data.
8//!
9//! Rust supports the notion of [Dynamically Sized Types](https://doc.rust-lang.org/reference/dynamically-sized-types.html), known as DSTs,
10//! which are types that have a size not known at compile time. DSTs are perfect to implement
11//! flexible array members. But unfortunately, Rust doesn't provide an out-of-the-box way to allocate
12//! instances of such types. This is where this crate comes in.
13//!
14//! You can apply the #[[`macro@make_dst_factory`]] attribute to your DST structs, which causes factory
15//! functions to be produced that let you easily and safely create instances of your DSTs.
16//!
17//! # Why Should You Care?
18//!
19//! Dynamically sized types aren't for everyone. You can't use them as local variables
20//! or put them in arrays or vectors, so they can be inconvenient to use. However, their value
21//! lies in situations where you have a lot of heap-allocated objects, as they can substantially
22//! reduce the memory footprint of your application. If you're building graphs, trees, or other
23//! dynamic data structures, you can often leverage DSTs to keep your individual nodes smaller
24//! and more efficient.
25//!
26//! # Examples
27//!
28//! Here's an example using an array as the last field of a struct:
29//!
30//! ```rust
31//! use dst_factory::make_dst_factory;
32//!
33//! #[make_dst_factory]
34//! struct User {
35//!     age: u32,
36//!     signing_key: [u8],
37//! }
38//!
39//! // allocate one user with a 4-byte key
40//! let a = User::build(33, [0, 1, 2, 3]);
41//!
42//! // allocate another user with a 5-byte key
43//! let b = User::build_from_slice(33, &[0, 1, 2, 3, 4]);
44//!
45//! // allocate another user, this time using an iterator
46//! let v = vec![0, 1, 2, 3, 4];
47//! let c = User::build(33, v.iter().copied());
48//!
49//! // destructure this user and compare its key to the vector
50//! // this has the advantage of iterating over u8, not &u8 or &mut u8.
51//! let (_age, signing_key) = User::destructure(c);
52//! assert!(signing_key.eq(v.into_iter()));
53//! ```
54//! Here's another example, this time using a string as the last field of a struct:
55//!
56//! ```rust
57//! use dst_factory::make_dst_factory;
58//!
59//! #[make_dst_factory]
60//! struct User {
61//!     age: u32,
62//!     name: str,
63//! }
64//!
65//! // allocate one user with a 5-character string
66//! let a = User::build(33, "Alice");
67//!
68//! // allocate another user with a 3-character string
69//! let b = User::build(33, "Bob");
70//! ```
71//! And finally, here's an example using a trait object as the last field of a struct:
72//! ```rust
73//! use dst_factory::make_dst_factory;
74//!
75//! // a trait we'll use in our DST
76//! trait NumberProducer {
77//!     fn get_number(&self) -> u32;
78//! }
79//!
80//! // an implementation of the trait we're going to use
81//! struct FortyTwoProducer;
82//! impl NumberProducer for FortyTwoProducer {
83//!     fn get_number(&self) -> u32 {
84//!         42
85//!     }
86//! }
87//!
88//! // another implementation of the trait we're going to use
89//! struct TenProducer;
90//! impl NumberProducer for TenProducer {
91//!     fn get_number(&self) -> u32 {
92//!         10
93//!     }
94//! }
95//!
96//! #[make_dst_factory]
97//! struct Node {
98//!     count: u32,
99//!     producer: dyn NumberProducer,
100//! }
101//!
102//! // allocate an instance with one implementation of the trait
103//! let a = Node::build(33, FortyTwoProducer{});
104//! assert_eq!(42, a.producer.get_number());
105//!
106//! // allocate an instance with another implementation of the trait
107//! let b = Node::build(33, TenProducer{});
108//! assert_eq!(10, b.producer.get_number());
109//! ```
110//!
111//! Because DSTs don't have a known size at compile time, you can't store them on the stack,
112//! and you can't pass them by value. As a result of these constraints, the factory functions
113//! always return boxed instances of the structs.
114//!
115//! # Attribute Features
116//!
117//! The common use case for the #[[`macro@make_dst_factory`]] attribute is to not pass any arguments.
118//! This results in a function called `build` when using a string or dynamic trait as the
119//! last field of the struct, and the functions `build`, `build_from_slice`, and `destructure` when using an array as the last
120//! field of the struct.
121//!
122//! The generated functions are private by default and have the following signatures:
123//!
124//! ```ignore
125//! // for arrays
126//! fn build<G>(field1, field2, ..., last_field: G) -> Box<Self>
127//! where
128//!     G: IntoIterator<Item = last_field_type>,
129//!     <G as IntoIterator>::IntoIter: ExactSizeIterator,
130//!
131//! fn build_from_slice(field1, field2, ..., last_field: &[last_field_type]) -> Box<Self>
132//! where
133//!     last_field_type: Copy + Sized;
134//!
135//! fn destructure(this: Box<Self>) -> (Type1, Type2, ..., SelfIter);
136//!
137//! // for strings
138//! fn build(field1, field2, ..., last_field: impl AsRef<str>) -> Box<Self>;
139//!
140//! // for traits
141//! fn build(field1, field2, ..., last_field: G) -> Box<Self>
142//! where
143//!     G: TraitName + Sized;
144//! ```
145//!
146//! The attribute lets you control the name of the generated functions, their
147//! visibility, and whether to generate code for the `no_std` environment. The general
148//! grammar is:
149//!
150//! ```ignore
151//! #[make_dst_factory(<base_factory_name> [, destructurer=<destructurer_name>] [, iterator=<iterator_name>] [, <visibility>] [, no_std] [, generic=<generic_name>])]
152//! ```
153//!
154//! Some examples:
155//!
156//! ```ignore
157//! // The generated functions will be public and called `build`, `build_from_slice`, and `destructure`.
158//! #[make_dst_factory(pub)]
159//!
160//! // The generated functions will be private and called `create`, `create_from_slice`, and `destructure`.
161//! #[make_dst_factory(create)]
162//!
163//! // The generated functions will be private and called `create`, `create_from_slice`, and `destroy`.
164//! #[make_dst_factory(create, destructurer = destroy)]
165//!
166//! // The generated functions will be public and called `create`, `create_from_slice`, and `destructure`
167//! #[make_dst_factory(create, pub)]
168//!
169//! // The generated functions will be private, called `create`, `create_from_slice`, and `destructure`, and support the `no_std` environment
170//! #[make_dst_factory(create, no_std)]
171//!
172//! // The generated functions will be private, called `create`, `create_from_slice`, and `destructure`,
173//! // support the `no_std` environment, and will have generic types called `X`.
174//! #[make_dst_factory(create, no_std, generic=X)]
175//! ```
176//!
177//! # Other Features
178//!
179//! You can use the #[[`macro@make_dst_factory`]] attribute on structs with the normal Rust
180//! representation or C representation (`#[repr(C)]`), with any padding and alignment
181//! specification. See the Rust reference on [Type Layout](https://doc.rust-lang.org/reference/type-layout.html)
182//! for more details.
183//!
184//! # Error Conditions
185//!
186//! The #[[`macro@make_dst_factory`]] attribute produces a compile-time error if:
187//!
188//! - It's applied to anything other than a regular struct or a tuple struct.
189//! - Its arguments are malformed (e.g., incorrect visibility keyword, too many arguments).
190//! - The struct has no fields.
191//! - The last field of the struct is not a slice (`[T]`), a string (`str`), or a trait object (`dyn Trait`).
192//! - The resulting struct exceeds the maximum size allowed of `isize::MAX`.
193//!
194//! # Acknowledgements
195//!
196//! Many thanks to <https://github.com/scottmcm> for his invaluable help getting the factory methods
197//! in top shape.
198
199extern crate proc_macro;
200mod macro_args;
201
202use macro_args::MacroArgs;
203use proc_macro2::{Span, TokenStream};
204use quote::{ToTokens, format_ident, quote, quote_spanned};
205use syn::token::{Comma, Where};
206use syn::{
207    Field, Fields, Generics, Ident, Index, ItemStruct, Path, Type, TypePath, parse::Result as SynResult, punctuated::Punctuated,
208    spanned::Spanned,
209};
210
211enum TailKind {
212    Slice(Box<Type>),
213    Str,
214    TraitObject(Path),
215}
216
217enum FieldIdent {
218    Named(Ident),
219    Unnamed(Index),
220}
221
222impl ToTokens for FieldIdent {
223    fn to_tokens(&self, tokens: &mut TokenStream) {
224        match self {
225            Self::Named(ident) => ident.to_tokens(tokens),
226            Self::Unnamed(index) => index.to_tokens(tokens),
227        }
228    }
229}
230
231struct StructInfo<'a> {
232    struct_name: &'a Ident,
233    struct_generics: &'a Generics,
234    header_fields: Box<[&'a Field]>,
235    header_field_idents: Box<[FieldIdent]>,
236    header_param_idents: Box<[Ident]>,
237    header_types: Vec<Type>,
238    tail_field: &'a Field,
239    tail_field_ident: FieldIdent,
240    tail_param_ident: Ident,
241    tail_kind: TailKind,
242}
243
244impl<'a> StructInfo<'a> {
245    fn new(input_struct: &'a ItemStruct) -> SynResult<Self> {
246        match &input_struct.fields {
247            Fields::Named(named_fields) => Self::process_fields(input_struct, &named_fields.named),
248            Fields::Unnamed(unnamed_fields) => Self::process_fields(input_struct, &unnamed_fields.unnamed),
249            Fields::Unit => Err(syn::Error::new_spanned(input_struct, "Unit structs are not supported")),
250        }
251    }
252
253    fn process_fields(input_struct: &'a ItemStruct, fields: &'a Punctuated<Field, Comma>) -> SynResult<Self> {
254        if fields.is_empty() {
255            return Err(syn::Error::new_spanned(input_struct, "Struct must have at least one field"));
256        }
257
258        let header_fields: Vec<_> = fields.iter().take(fields.len() - 1).collect();
259        let tail_field = fields.last().unwrap();
260
261        let tail_kind = match &tail_field.ty {
262            Type::Path(type_path) if type_path.path.is_ident("str") => TailKind::Str,
263            Type::Path(TypePath { path, .. }) if path.segments.last().is_some_and(|s| s.ident == "str") => TailKind::Str,
264            Type::Slice(type_slice) => TailKind::Slice(type_slice.elem.clone()),
265
266            Type::TraitObject(type_trait_object) => {
267                // Verify that the trait object is not a higher-rank trait bound
268                for bound in &type_trait_object.bounds {
269                    if let syn::TypeParamBound::Trait(trait_bound) = bound {
270                        if trait_bound.lifetimes.is_some() {
271                            return Err(syn::Error::new_spanned(
272                                trait_bound,
273                                "Higher-rank trait bounds (e.g., `for<'a> dyn Trait<'a>`) are not supported for the tail field.",
274                            ));
275                        }
276                    }
277                }
278
279                // Extract the primary trait path from the bounds.
280                let trait_path = type_trait_object
281                    .bounds
282                    .iter()
283                    .find_map(|bound| {
284                        if let syn::TypeParamBound::Trait(trait_bound) = bound {
285                            Some(&trait_bound.path)
286                        } else {
287                            None // Not a trait bound (e.g., a lifetime bound like `'a`).
288                        }
289                    })
290                    .unwrap();
291
292                TailKind::TraitObject(trait_path.clone())
293            }
294
295            _ => {
296                return Err(syn::Error::new_spanned(
297                    &tail_field.ty,
298                    "Last field must be a dynamically sized type like [T], str, or dyn Trait",
299                ));
300            }
301        };
302
303        let header_param_idents: Vec<_> = header_fields
304            .iter()
305            .enumerate()
306            .map(|(i, field)| field.ident.as_ref().map_or_else(|| format_ident!("f{i}"), Clone::clone))
307            .collect();
308
309        let header_field_idents: Vec<_> = header_fields
310            .iter()
311            .enumerate()
312            .map(|(i, field)| {
313                field
314                    .ident
315                    .as_ref()
316                    .map_or_else(|| FieldIdent::Unnamed(Index::from(i)), |ident| FieldIdent::Named(ident.clone()))
317            })
318            .collect();
319
320        let header_types: Vec<_> = header_fields.iter().map(|field| field.ty.clone()).collect();
321
322        let tail_param_ident = tail_field
323            .ident
324            .as_ref()
325            .map_or_else(|| format_ident!("f{}", header_fields.len()), Clone::clone);
326
327        let tail_field_ident = tail_field.ident.as_ref().map_or_else(
328            || FieldIdent::Unnamed(Index::from(header_fields.len())),
329            |ident| FieldIdent::Named(ident.clone()),
330        );
331
332        Ok(Self {
333            struct_name: &input_struct.ident,
334            struct_generics: &input_struct.generics,
335            header_fields: header_fields.into_boxed_slice(),
336            header_field_idents: header_field_idents.into_boxed_slice(),
337            header_param_idents: header_param_idents.into_boxed_slice(),
338            header_types,
339            tail_field,
340            tail_field_ident,
341            tail_param_ident,
342            tail_kind,
343        })
344    }
345}
346
347fn header_layout(macro_args: &MacroArgs, struct_info: &StructInfo, for_trait: bool) -> TokenStream {
348    let tail_field_ident = &struct_info.tail_field_ident;
349
350    let header_field_types: Vec<_> = struct_info.header_fields.iter().map(|field| &field.ty).collect();
351
352    if header_field_types.is_empty() {
353        return quote! {
354            let layout = ::core::alloc::Layout::from_size_align(0, 1).unwrap();
355        };
356    }
357
358    let fat_payload = if for_trait {
359        quote! { vtable }
360    } else {
361        quote! { 0_usize }
362    };
363
364    let tail_type = match &struct_info.tail_kind {
365        TailKind::Slice(elem_type) => quote! { #elem_type },
366        TailKind::Str => quote! { u8 },
367        TailKind::TraitObject(_) => {
368            let generic_name = &macro_args.generic_name;
369            quote! { #generic_name }
370        }
371    };
372
373    quote! {
374        let buffer = ::core::mem::MaybeUninit::<(#( #header_field_types, )* #tail_type)>::uninit();
375        let (offset, align) = unsafe {
376            let head_ptr: *const Self = ::core::mem::transmute((&raw const buffer, #fat_payload));
377            let tail_ptr = &raw const (*head_ptr).#tail_field_ident;
378            (
379                (tail_ptr as *const u8).offset_from_unsigned(head_ptr as *const u8),
380                ::core::mem::align_of_val::<Self>(&*head_ptr)
381            )
382        };
383
384        let layout = ::core::alloc::Layout::from_size_align(offset, align).unwrap();
385    }
386}
387
388fn tail_layout<T: quote::ToTokens>(tail_type: &T, span: Span) -> TokenStream {
389    quote_spanned! { span => ::core::alloc::Layout::array::<#tail_type>(len).expect("Array exceeds maximum size allowed of isize::MAX") }
390}
391
392fn dealloc_path(no_std: bool) -> TokenStream {
393    if no_std {
394        quote! { ::alloc::alloc::dealloc }
395    } else {
396        quote! { ::std::alloc::dealloc }
397    }
398}
399
400fn box_path(no_std: bool) -> TokenStream {
401    if no_std {
402        quote! { ::alloc::boxed::Box }
403    } else {
404        quote! { ::std::boxed::Box }
405    }
406}
407
408fn guard_type(macro_args: &MacroArgs) -> TokenStream {
409    let dealloc_path = dealloc_path(macro_args.no_std);
410
411    quote! {
412        struct Guard<T> {
413            mem_ptr: *mut u8,
414            tail_ptr: *mut T,
415            initialized: usize,
416            layout: ::core::alloc::Layout,
417        }
418
419        impl<T> Drop for Guard<T> {
420            fn drop(&mut self) {
421                unsafe {
422                    let slice_ptr = ::core::ptr::slice_from_raw_parts_mut(self.tail_ptr, self.initialized);
423                    ::core::ptr::drop_in_place(slice_ptr);
424                    #dealloc_path(self.mem_ptr, self.layout);
425                }
426            }
427        }
428    }
429}
430
431fn header_params(struct_info: &StructInfo) -> Vec<TokenStream> {
432    struct_info
433        .header_fields
434        .iter()
435        .enumerate()
436        .map(|(i, field)| {
437            let param_ident = &struct_info.header_param_idents[i];
438            let field_ty = &field.ty;
439            quote! { #param_ident: #field_ty }
440        })
441        .collect()
442}
443
444fn header_field_writes(struct_info: &StructInfo) -> Vec<TokenStream> {
445    struct_info
446        .header_field_idents
447        .iter()
448        .enumerate()
449        .map(|(i, field_ident)| {
450            let tuple_idx = syn::Index::from(i);
451            quote! { ::core::ptr::write_unaligned(&raw mut ((*fat_ptr).#field_ident), args.#tuple_idx);}
452        })
453        .collect()
454}
455
456fn args_tuple_assignment(struct_info: &StructInfo) -> TokenStream {
457    let header_param_idents = &struct_info.header_param_idents;
458    let tail_param_ident = &struct_info.tail_param_ident;
459
460    quote! {
461        let args = ( #( #header_param_idents, )* #tail_param_ident, );
462    }
463}
464
465fn alloc(no_std: bool) -> TokenStream {
466    let (alloc_path, handle_alloc_error) = if no_std {
467        (quote! { ::alloc::alloc::alloc }, quote! { panic!("out of memory") })
468    } else {
469        (quote! { ::std::alloc::alloc }, quote! { ::std::alloc::handle_alloc_error(layout) })
470    };
471
472    quote! {
473        let mem_ptr = #alloc_path(layout);
474        if mem_ptr.is_null() {
475            #handle_alloc_error
476        }
477    }
478}
479
480fn alloc_zst(box_path: &TokenStream, for_trait: bool) -> TokenStream {
481    let mem_ptr = quote! { let mem_ptr = ::core::ptr::NonNull::<()>::dangling().as_ptr(); };
482
483    let fat_ptr = if for_trait {
484        quote! { let fat_ptr = ::core::mem::transmute::<(*mut (), *const ()), *mut Self>((mem_ptr, vtable)); }
485    } else {
486        quote! { let fat_ptr = ::core::mem::transmute::<(*mut (), usize), *mut Self>((mem_ptr, 0_usize)); }
487    };
488
489    let box_from_raw = quote! { #box_path::from_raw(fat_ptr) };
490
491    quote! {
492        #mem_ptr
493        #fat_ptr
494        #box_from_raw
495    }
496}
497
498fn factory_for_slice_arg(macro_args: &MacroArgs, struct_info: &StructInfo, tail_elem_type: &Type) -> TokenStream {
499    let copy_bound_tokens: syn::WherePredicate = syn::parse_quote_spanned! {tail_elem_type.span()=>
500        #tail_elem_type: ::core::marker::Copy
501    };
502
503    let mut factory_where_clause = struct_info.struct_generics.where_clause.as_ref().map_or_else(
504        || syn::WhereClause {
505            where_token: Where::default(),
506            predicates: Punctuated::new(),
507        },
508        Clone::clone,
509    );
510
511    factory_where_clause.predicates.push(copy_bound_tokens);
512
513    let alloc = alloc(macro_args.no_std);
514    let box_path = box_path(macro_args.no_std);
515    let make_zst = alloc_zst(&box_path, false);
516
517    let tail_layout = tail_layout(tail_elem_type, struct_info.tail_field.ty.span());
518    let header_layout = header_layout(macro_args, struct_info, false);
519    let tuple_assignment = args_tuple_assignment(struct_info);
520    let header_field_writes = header_field_writes(struct_info);
521    let header_params = header_params(struct_info);
522
523    let factory_name = format_ident!("{}_from_slice", &macro_args.base_factory_name);
524    let visibility = &macro_args.visibility;
525
526    let tail_param = &struct_info.tail_param_ident;
527    let tail_field = &struct_info.tail_field_ident;
528    let struct_name = &struct_info.struct_name;
529    let tail_args_tuple_idx = Index::from(struct_info.header_fields.len());
530
531    let factory_doc = format!("Creates an instance of `Box<{struct_name}>`.");
532
533    quote! {
534        #[doc = #factory_doc]
535        #[allow(clippy::let_unit_value)]
536        #[allow(clippy::zst_offset)]
537        #[allow(clippy::transmute_undefined_repr)]
538        #visibility fn #factory_name (
539            #( #header_params, )*
540            #tail_param: &[#tail_elem_type]
541        ) -> #box_path<Self> #factory_where_clause {
542            #tuple_assignment
543
544            let s = args.#tail_args_tuple_idx.as_ref();
545            let len = s.len();
546
547            #header_layout
548            let layout = layout.extend(#tail_layout).expect("Struct exceeds maximum size allowed of isize::MAX").0;
549            let layout = layout.pad_to_align();
550
551            unsafe {
552                if layout.size() == 0 {
553                    #make_zst
554                } else {
555                    #alloc
556
557                    let fat_ptr = ::core::mem::transmute::<(*mut u8, usize), *mut Self>((mem_ptr, len));
558                    ::core::debug_assert_eq!(::core::alloc::Layout::for_value(&*fat_ptr), layout);
559
560                    #( #header_field_writes )*
561
562                    // Copy the slice content into the tail field
563                    let tail_ptr = (&raw mut (*fat_ptr).#tail_field).cast::<#tail_elem_type>();
564                    ::core::ptr::copy_nonoverlapping(s.as_ptr(), tail_ptr, len);
565
566                    #box_path::from_raw(fat_ptr)
567                }
568            }
569        }
570    }
571}
572
573fn factory_for_iter_arg(macro_args: &MacroArgs, struct_info: &StructInfo, tail_type: &Type) -> TokenStream {
574    let guard_type_tokens = guard_type(macro_args);
575    let alloc = alloc(macro_args.no_std);
576    let box_path = box_path(macro_args.no_std);
577    let make_zst = alloc_zst(&box_path, false);
578
579    let tail_layout = tail_layout(tail_type, struct_info.tail_field.ty.span());
580    let header_layout = header_layout(macro_args, struct_info, false);
581    let tuple_assignment = args_tuple_assignment(struct_info);
582    let header_field_writes = header_field_writes(struct_info);
583    let header_params = header_params(struct_info);
584
585    let visibility = &macro_args.visibility;
586    let factory_name = &macro_args.base_factory_name;
587    let iter_generic_param = &macro_args.generic_name;
588
589    let tail_param = &struct_info.tail_param_ident;
590    let tail_field = &struct_info.tail_field_ident;
591    let struct_name = &struct_info.struct_name;
592    let tail_args_tuple_idx = Index::from(struct_info.header_fields.len());
593
594    let factory_doc = format!("Creates an instance of `Box<{struct_name}>`.");
595
596    quote! {
597        #[doc = #factory_doc]
598        #[allow(clippy::let_unit_value)]
599        #[allow(clippy::zst_offset)]
600        #[allow(clippy::transmute_undefined_repr)]
601        #visibility fn #factory_name <#iter_generic_param> (
602            #( #header_params, )*
603            #tail_param: #iter_generic_param
604        ) -> #box_path<Self>
605        where
606            #iter_generic_param: ::core::iter::IntoIterator<Item = #tail_type>,
607            <#iter_generic_param as ::core::iter::IntoIterator>::IntoIter: ::core::iter::ExactSizeIterator
608        {
609            #guard_type_tokens
610            #tuple_assignment
611
612            let iter = args.#tail_args_tuple_idx.into_iter();
613            let len = iter.len();
614
615            #header_layout
616            let layout = layout.extend(#tail_layout).expect("Struct exceeds maximum size allowed of isize::MAX").0;
617            let layout = layout.pad_to_align();
618
619            unsafe {
620                if layout.size() == 0 {
621                    #make_zst
622                } else {
623                    #alloc
624
625                    let fat_ptr = ::core::mem::transmute::<(*mut u8, usize), *mut Self>((mem_ptr, len));
626                    ::core::debug_assert_eq!(::core::alloc::Layout::for_value(&*fat_ptr), layout);
627
628                    #( #header_field_writes )*
629
630                    // Write each element from the iterator into the tail field
631                    let tail_ptr = ::core::ptr::addr_of_mut!((*fat_ptr).#tail_field).cast::<#tail_type>();
632                    let mut guard = Guard { mem_ptr, tail_ptr, layout, initialized: 0 };
633                    iter.for_each(|element| {
634                        if guard.initialized == len {
635                            panic!("Mismatch between iterator-reported length and the number of items produced by the iterator");
636                        }
637
638                        ::core::ptr::write(tail_ptr.add(guard.initialized), element);
639                        guard.initialized += 1;
640                    });
641
642                    if guard.initialized != len {
643                        panic!("Mismatch between iterator-reported length and the number of items produced by the iterator");
644                    }
645
646                    ::std::mem::forget(guard);
647
648                    #box_path::from_raw(fat_ptr)
649                }
650            }
651        }
652    }
653}
654
655fn destructurer_iterator_type(macro_args: &MacroArgs, struct_info: &StructInfo, tail_type: &Type) -> TokenStream {
656    let dealloc_path = dealloc_path(macro_args.no_std);
657
658    let visibility = &macro_args.visibility;
659    let iterator_name = macro_args
660        .iterator_name
661        .clone()
662        .unwrap_or_else(|| Ident::new(&format!("{}Iter", struct_info.struct_name), proc_macro2::Span::call_site()));
663
664    let struct_name = &struct_info.struct_name;
665    let struct_generics = &struct_info.struct_generics;
666    let (impl_generics, ty_generics, where_clause) = struct_info.struct_generics.split_for_impl();
667
668    let factory_doc = format!("Iterator type for a destructured `Box<{struct_name}>`");
669
670    quote! {
671        #[doc = #factory_doc]
672        #visibility struct #iterator_name #struct_generics #where_clause {
673            ptr: *mut #tail_type,
674            index: usize,
675            len: usize,
676
677            free_ptr: *mut u8,
678            layout: ::core::alloc::Layout,
679        }
680
681        impl #impl_generics ::core::iter::Iterator for #iterator_name #ty_generics #where_clause {
682            type Item = #tail_type;
683
684            fn next(&mut self) -> Option<Self::Item> {
685                if self.index >= self.len {
686                    return None;
687                }
688                #[allow(clippy::zst_offset)]
689                let value = unsafe { self.ptr.add(self.index).read() };
690                self.index += 1;
691                Some(value)
692            }
693        }
694
695        impl #impl_generics ::core::ops::Drop for #iterator_name #ty_generics #where_clause {
696            fn drop(&mut self) {
697                unsafe { #dealloc_path(self.free_ptr, self.layout) }
698            }
699        }
700    }
701}
702
703fn destructurer_with_iter(macro_args: &MacroArgs, struct_info: &StructInfo) -> TokenStream {
704    let box_path = box_path(macro_args.no_std);
705
706    let header_fields = &struct_info.header_field_idents;
707    let header_types = &struct_info.header_types;
708
709    let visibility = &macro_args.visibility;
710    let destructurer_name = &macro_args.base_destructurer_name;
711
712    let tail_field = &struct_info.tail_field_ident;
713    let struct_name = &struct_info.struct_name;
714    let iterator_name = macro_args
715        .iterator_name
716        .clone()
717        .unwrap_or_else(|| Ident::new(&format!("{}Iter", struct_info.struct_name), proc_macro2::Span::call_site()));
718    let (_impl_generics, ty_generics, _where_clause) = struct_info.struct_generics.split_for_impl();
719
720    let factory_doc = format!("Destructures an instance of `Box<{struct_name}>`, returning the tail slice as an iterator.");
721
722    quote! {
723        #[doc = #factory_doc]
724        #visibility fn #destructurer_name(
725            this: #box_path<Self>,
726        ) -> ( #( #header_types, )* #iterator_name #ty_generics)
727        {
728            let layout = ::core::alloc::Layout::for_value(&*this);
729            let len = this.#tail_field.len();
730            let this = #box_path::into_raw(this);
731            unsafe {
732                (
733                    #( (&raw mut (*this).#header_fields).read(), )*
734                    #iterator_name {
735                        ptr: (*this).#tail_field.as_mut_ptr(),
736                        index: 0,
737                        len,
738
739                        free_ptr: this.cast(),
740                        layout,
741                    }
742                )
743            }
744        }
745    }
746}
747
748fn factory_for_str_arg(macro_args: &MacroArgs, struct_info: &StructInfo) -> TokenStream {
749    let alloc = alloc(macro_args.no_std);
750    let box_path = box_path(macro_args.no_std);
751    let make_zst = alloc_zst(&box_path, false);
752
753    let tail_layout = tail_layout(&quote! { u8 }, struct_info.tail_field.ty.span());
754    let header_layout = header_layout(macro_args, struct_info, false);
755    let tuple_assignment = args_tuple_assignment(struct_info);
756    let header_field_writes = header_field_writes(struct_info);
757    let header_params = header_params(struct_info);
758
759    let factory_name = &macro_args.base_factory_name;
760    let visibility = &macro_args.visibility;
761
762    let struct_name = &struct_info.struct_name;
763    let tail_param = &struct_info.tail_param_ident;
764    let tail_field = &struct_info.tail_field_ident;
765    let tail_type = &struct_info.tail_field.ty;
766    let tail_args_tuple_idx = Index::from(struct_info.header_fields.len());
767
768    let factory_doc = format!("Creates an instance of `Box<{struct_name}>`.");
769
770    quote! {
771        #[doc = #factory_doc]
772        #[allow(clippy::let_unit_value)]
773        #[allow(clippy::zst_offset)]
774        #[allow(clippy::transmute_undefined_repr)]
775        #visibility fn #factory_name(
776            #( #header_params, )*
777            #tail_param: impl ::core::convert::AsRef<str>
778        ) -> #box_path<Self> {
779            #tuple_assignment
780
781            ::core::assert_eq!(::core::any::TypeId::of::<#tail_type>(), ::core::any::TypeId::of::<str>());
782            let s = args.#tail_args_tuple_idx.as_ref();
783            let len = s.len();
784
785            #header_layout
786            let layout = layout.extend(#tail_layout).expect("Struct exceeds maximum size allowed of isize::MAX").0;
787            let layout = layout.pad_to_align();
788
789            unsafe {
790                if layout.size() == 0 {
791                    #make_zst
792                } else {
793                    #alloc
794
795                    let fat_ptr = ::core::mem::transmute::<(*mut u8, usize), *mut Self>((mem_ptr, len));
796                    ::core::debug_assert_eq!(::core::alloc::Layout::for_value(&*fat_ptr), layout);
797
798                    #( #header_field_writes )*
799
800                    // copy the string data into the tail field
801                    let tail_ptr = (&raw mut (*fat_ptr).#tail_field).cast::<u8>();
802                    ::core::ptr::copy_nonoverlapping(s.as_ptr(), tail_ptr, len);
803
804                    #box_path::from_raw(fat_ptr)
805                }
806            }
807        }
808    }
809}
810
811fn factory_for_trait_arg(macro_args: &MacroArgs, struct_info: &StructInfo, trait_path: &Path) -> TokenStream {
812    let alloc = alloc(macro_args.no_std);
813    let box_path = box_path(macro_args.no_std);
814    let make_zst = alloc_zst(&box_path, true);
815
816    let header_layout = header_layout(macro_args, struct_info, true);
817    let tuple_assignment = args_tuple_assignment(struct_info);
818    let header_field_writes = header_field_writes(struct_info);
819    let header_params = header_params(struct_info);
820
821    let factory_name = &macro_args.base_factory_name;
822    let trait_generic = &macro_args.generic_name;
823    let visibility = &macro_args.visibility;
824
825    let struct_name = &struct_info.struct_name;
826    let tail_param = &struct_info.tail_param_ident;
827    let tail_field = &struct_info.tail_field_ident;
828    let tail_args_tuple_idx = Index::from(struct_info.header_fields.len());
829
830    let factory_doc = format!("Builds an instance of `Box<{struct_name}>`.");
831
832    quote! {
833        #[doc = #factory_doc]
834        #[allow(clippy::let_unit_value)]
835        #[allow(clippy::zst_offset)]
836        #[allow(clippy::transmute_undefined_repr)]
837        #visibility fn #factory_name <#trait_generic> (
838            #( #header_params, )*
839            #tail_param: #trait_generic
840        ) -> #box_path<Self>
841        where
842            #trait_generic: #trait_path + Sized
843        {
844            #tuple_assignment
845
846            let s = args.#tail_args_tuple_idx;
847            let trait_object: &dyn #trait_path = &s;
848            let (_, vtable): (*const #trait_generic, *const ()) = unsafe { ::core::mem::transmute(trait_object) };
849
850            #header_layout
851            let layout = layout.extend(::core::alloc::Layout::new::<#trait_generic>()).expect("Struct exceeds maximum size allowed of isize::MAX").0;
852            let layout = layout.pad_to_align();
853
854            unsafe {
855                if layout.size() == 0 {
856                    #make_zst
857                } else {
858                    #alloc
859
860                    let fat_ptr = ::core::mem::transmute::<(*mut u8, *const ()), *mut Self>((mem_ptr, vtable));
861                    ::core::debug_assert_eq!(::core::alloc::Layout::for_value(&*fat_ptr), layout);
862
863                    #( #header_field_writes )*
864
865                    let tail_ptr = (&raw mut (*fat_ptr).#tail_field).cast::<#trait_generic>();
866                    ::core::ptr::copy_nonoverlapping(::core::ptr::addr_of!(s), tail_ptr, 1);
867
868                    #box_path::from_raw(fat_ptr)
869                }
870            }
871        }
872    }
873}
874
875fn make_dst_factory_impl(attr_args: TokenStream, item: TokenStream) -> SynResult<TokenStream> {
876    let macro_args = MacroArgs::parse(attr_args)?;
877    let input_struct: ItemStruct = syn::parse2(item)?;
878    let struct_info = StructInfo::new(&input_struct)?;
879
880    let mut factories = Vec::new();
881    let mut iterator_type = None;
882    match &struct_info.tail_kind {
883        TailKind::Slice(elem_type) => {
884            factories.push(factory_for_iter_arg(&macro_args, &struct_info, elem_type));
885            factories.push(factory_for_slice_arg(&macro_args, &struct_info, elem_type));
886            factories.push(destructurer_with_iter(&macro_args, &struct_info));
887            iterator_type = Some(destructurer_iterator_type(&macro_args, &struct_info, elem_type));
888        }
889
890        TailKind::Str => {
891            factories.push(factory_for_str_arg(&macro_args, &struct_info));
892        }
893
894        TailKind::TraitObject(trait_path) => {
895            factories.push(factory_for_trait_arg(&macro_args, &struct_info, trait_path));
896        }
897    }
898
899    let (impl_generics, ty_generics, where_clause) = struct_info.struct_generics.split_for_impl();
900    let struct_name_ident = struct_info.struct_name;
901
902    Ok(quote! {
903        #input_struct
904
905        impl #impl_generics #struct_name_ident #ty_generics #where_clause {
906            #( #factories )*
907        }
908
909        #iterator_type
910    })
911}
912
913/// Generate factory functions for dynamically sized types (DST) structs.
914///
915/// This macro, when applied to a struct whose last field is a slice (`[T]`), `str`, or `dyn Trait`,
916/// generates an `impl` block with functions to construct instances of the structs with
917/// dynamically sized tail data.
918///
919/// This attribute is generally used without any arguments, but more advanced
920/// uses can pass arguments with the following grammar:
921///
922/// ```ignore
923/// #[make_dst_factory(<base_factory_name>[, <visibility>] [, no_std] [, generic=<generic_name>])]
924/// ```
925/// Refer to the [crate-level documentation](crate) for more details and example uses.
926///
927/// # Usage
928///
929/// ```rust
930/// use dst_factory::make_dst_factory;
931///
932/// #[make_dst_factory]
933/// struct MyStruct {
934///     id: u32,
935///     data: [u8],
936/// }
937///
938/// // With custom factory name and public visibility
939/// #[make_dst_factory(create, pub)]
940/// struct PublicStruct {
941///     id: u32,
942///     data: str,
943/// }
944///
945/// trait MyTrait {}
946///
947/// // With a trait object as the tail field
948/// #[make_dst_factory]
949/// struct TraitStruct {
950///     id: u32,
951///     handler: dyn MyTrait,
952/// }
953/// ```
954#[proc_macro_attribute]
955pub fn make_dst_factory(attr_args: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
956    let result = make_dst_factory_impl(attr_args.into(), item.into());
957    result.unwrap_or_else(|err| err.to_compile_error()).into()
958}