byteable_derive/
lib.rs

1//! Procedural macro for deriving the `Byteable` trait.
2//!
3//! This crate provides the `#[derive(UnsafeByteableTransmute)]` procedural macro for automatically
4//! implementing the `Byteable` trait on structs.
5
6use proc_macro_crate::{FoundCrate, crate_name};
7use proc_macro2::Span;
8use quote::quote;
9use syn::{Data, DeriveInput, Fields, Ident, Meta, parse_macro_input};
10
11/// Derives the `Byteable` trait for a struct using `transmute`.
12///
13/// This procedural macro automatically implements the `Byteable` trait for structs by using
14/// `std::mem::transmute` to convert between the struct and a byte array. This provides
15/// zero-overhead serialization but requires careful attention to memory layout and safety.
16///
17/// # Safety
18///
19/// This macro generates `unsafe` code using `std::mem::transmute`. You **must** ensure:
20///
21/// 1. **The struct has an explicit memory layout**: Use `#[repr(C)]`, `#[repr(C, packed)]`,
22///    or `#[repr(transparent)]` to ensure a well-defined layout.
23///
24/// 2. **All byte patterns are valid**: Every possible combination of bytes must represent
25///    a valid value for your struct. This generally means:
26///    - Primitive numeric types are fine (`u8`, `i32`, `f64`, etc.)
27///    - Endianness wrappers are fine (`BigEndian<T>`, `LittleEndian<T>`)
28///    - Arrays of the above are fine
29///    - Types with invalid bit patterns are **NOT** safe (`bool`, `char`, enums with
30///      discriminants, references, `NonZero*` types, etc.)
31///
32/// 3. **No padding with uninitialized memory**: When using `#[repr(C)]` without `packed`,
33///    padding bytes might contain uninitialized memory. Use `#[repr(C, packed)]` to avoid
34///    padding, or ensure all fields are properly aligned.
35///
36/// 4. **No complex types**: Do **not** use this with:
37///    - Types containing pointers or references (`&T`, `Box<T>`, `Vec<T>`, `String`, etc.)
38///    - Types with invariants (`NonZero*`, `bool`, `char`, enums with fields, etc.)
39///    - Types implementing `Drop`
40///
41/// # Requirements
42///
43/// The struct must:
44/// - Have a known size at compile time (no `dyn` traits or unsized fields)
45/// - Not contain any generic type parameters (or they must implement `Byteable`)
46///
47/// # Examples
48///
49/// ## Basic usage
50///
51/// ```
52/// # #[cfg(feature = "derive")]
53/// use byteable::{Byteable, UnsafeByteableTransmute};
54///
55/// # #[cfg(feature = "derive")]
56/// #[derive(UnsafeByteableTransmute, Debug, PartialEq)]
57/// #[repr(C, packed)]
58/// struct Color {
59///     r: u8,
60///     g: u8,
61///     b: u8,
62///     a: u8,
63/// }
64///
65/// # #[cfg(feature = "derive")]
66/// # fn example() {
67/// let color = Color { r: 255, g: 128, b: 64, a: 255 };
68/// let bytes = color.to_byte_array();
69/// assert_eq!(bytes, [255, 128, 64, 255]);
70///
71/// let restored = Color::from_byte_array(bytes);
72/// assert_eq!(restored, color);
73/// # }
74/// ```
75///
76/// ## With endianness markers
77///
78/// ```
79/// # #[cfg(feature = "derive")]
80/// use byteable::{Byteable, BigEndian, LittleEndian, UnsafeByteableTransmute};
81///
82/// # #[cfg(feature = "derive")]
83/// #[derive(UnsafeByteableTransmute, Debug)]
84/// #[repr(C, packed)]
85/// struct NetworkPacket {
86///     magic: BigEndian<u32>,           // Network byte order
87///     version: u8,
88///     flags: u8,
89///     payload_len: LittleEndian<u16>,  // Different endianness for payload
90///     data: [u8; 16],
91/// }
92///
93/// # #[cfg(feature = "derive")]
94/// # fn example() {
95/// let packet = NetworkPacket {
96///     magic: 0x12345678.into(),
97///     version: 1,
98///     flags: 0,
99///     payload_len: 100.into(),
100///     data: [0; 16],
101/// };
102///
103/// let bytes = packet.to_byte_array();
104/// // magic is big-endian: [0x12, 0x34, 0x56, 0x78]
105/// // payload_len is little-endian: [100, 0]
106/// # }
107/// ```
108///
109/// ## With nested structs
110///
111/// ```
112/// # #[cfg(feature = "derive")]
113/// use byteable::{Byteable, UnsafeByteableTransmute};
114///
115/// # #[cfg(feature = "derive")]
116/// #[derive(UnsafeByteableTransmute, Debug, Clone, Copy)]
117/// #[repr(C, packed)]
118/// struct Point {
119///     x: i32,
120///     y: i32,
121/// }
122///
123/// # #[cfg(feature = "derive")]
124/// #[derive(UnsafeByteableTransmute, Debug)]
125/// #[repr(C, packed)]
126/// struct Line {
127///     start: Point,
128///     end: Point,
129/// }
130///
131/// # #[cfg(feature = "derive")]
132/// # fn example() {
133/// let line = Line {
134///     start: Point { x: 0, y: 0 },
135///     end: Point { x: 10, y: 20 },
136/// };
137///
138/// let bytes = line.to_byte_array();
139/// assert_eq!(bytes.len(), 16); // 4 i32s × 4 bytes each
140/// # }
141/// ```
142///
143/// # Common Mistakes
144///
145/// ## Missing repr attribute
146///
147/// ```compile_fail
148/// # #[cfg(feature = "derive")]
149/// use byteable::UnsafeByteableTransmute;
150///
151/// # #[cfg(feature = "derive")]
152/// #[derive(UnsafeByteableTransmute)]  // No #[repr(...)] - undefined layout!
153/// struct Bad {
154///     x: u32,
155///     y: u16,
156/// }
157/// ```
158///
159/// ## Using invalid types
160///
161/// ```compile_fail
162/// # #[cfg(feature = "derive")]
163/// use byteable::UnsafeByteableTransmute;
164///
165/// # #[cfg(feature = "derive")]
166/// #[derive(UnsafeByteableTransmute)]
167/// #[repr(C, packed)]
168/// struct Bad {
169///     valid: bool,  // bool has invalid bit patterns (only 0 and 1 are valid)
170/// }
171/// ```
172///
173/// ## Using types with pointers
174///
175/// ```compile_fail
176/// # #[cfg(feature = "derive")]
177/// use byteable::UnsafeByteableTransmute;
178///
179/// # #[cfg(feature = "derive")]
180/// #[derive(UnsafeByteableTransmute)]
181/// #[repr(C)]
182/// struct Bad {
183///     data: Vec<u8>,  // Contains a pointer - not safe to transmute!
184/// }
185/// ```
186///
187/// # See Also
188///
189/// - [`Byteable`](../byteable/trait.Byteable.html) - The trait being implemented
190/// - [`impl_byteable_via!`](../byteable/macro.impl_byteable_via.html) - For complex types
191/// - [`unsafe_byteable_transmute!`](../byteable/macro.unsafe_byteable_transmute.html) - Manual implementation macro
192#[proc_macro_derive(UnsafeByteableTransmute)]
193pub fn byteable_transmute_derive_macro(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
194    // Find the byteable crate name (handles renamed imports and when used within the crate itself)
195    let found_crate = crate_name("byteable").expect("my-crate is present in `Cargo.toml`");
196
197    // Determine the correct path to the Byteable trait and crate
198    let (byteable, byteable_crate) = match found_crate {
199        // If we're inside the byteable crate itself
200        FoundCrate::Itself => (quote!(::byteable::Byteable), quote!(::byteable)),
201        // Otherwise, use the actual crate name (handles renamed imports)
202        FoundCrate::Name(name) => {
203            let ident = Ident::new(&name, Span::call_site());
204            (quote!( #ident::Byteable ), quote!( #ident ))
205        }
206    };
207
208    // Parse the input token stream into a DeriveInput AST
209    let input: DeriveInput = parse_macro_input!(input);
210
211    // Extract the struct/enum identifier
212    let ident = &input.ident;
213
214    // Split generics for the impl block (handles generic types correctly)
215    let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
216
217    // Extract all field types to add ValidBytecastMarker bounds
218    let field_types: Vec<_> = match &input.data {
219        Data::Struct(data) => match &data.fields {
220            Fields::Named(fields) => fields.named.iter().map(|f| &f.ty).collect(),
221            Fields::Unnamed(fields) => fields.unnamed.iter().map(|f| &f.ty).collect(),
222            Fields::Unit => Vec::new(),
223        },
224        _ => Vec::new(),
225    };
226
227    // Build where clause that includes ValidBytecastMarker bounds for all fields
228    let extended_where_clause = if field_types.is_empty() {
229        where_clause.cloned()
230    } else {
231        let mut clauses = where_clause
232            .cloned()
233            .unwrap_or_else(|| syn::parse_quote! { where });
234
235        for field_ty in &field_types {
236            clauses.predicates.push(syn::parse_quote! {
237                #field_ty: #byteable_crate::ValidBytecastMarker
238            });
239        }
240        Some(clauses)
241    };
242
243    // Generate the Byteable trait implementation with safety checks
244    quote! {
245        impl #impl_generics #byteable for #ident #type_generics #extended_where_clause {
246            // The byte array type is a fixed-size array matching the struct size
247            type ByteArray = [u8; ::std::mem::size_of::<Self>()];
248
249            // Convert the struct to bytes using transmute (unsafe but zero-cost)
250            fn to_byte_array(self) -> Self::ByteArray {
251                unsafe { ::std::mem::transmute(self) }
252            }
253
254            // Convert bytes back to the struct using transmute (unsafe but zero-cost)
255            fn from_byte_array(byte_array: Self::ByteArray) -> Self {
256                unsafe { ::std::mem::transmute(byte_array) }
257            }
258        }
259    }
260    .into()
261}
262
263/// Derives a delegate pattern for `Byteable` by generating a raw struct with endianness markers.
264///
265/// This macro creates a companion `*Raw` struct with `#[repr(C, packed)]` that handles the actual
266/// byte conversion, while keeping your original struct clean and easy to work with. Fields can be
267/// annotated with `#[byteable(little_endian)]` or `#[byteable(big_endian)]` to specify endianness.
268///
269/// # Generated Code
270///
271/// For each struct, this macro generates:
272/// 1. A `*Raw` struct with `#[repr(C, packed)]` and endianness wrappers
273/// 2. `From<OriginalStruct>` for `OriginalStructRaw` implementation
274/// 3. `From<OriginalStructRaw>` for `OriginalStruct` implementation  
275/// 4. A `Byteable` implementation via `impl_byteable_via!` macro
276///
277/// # Attributes
278///
279/// - `#[byteable(little_endian)]` - Wraps the field in `LittleEndian<T>`
280/// - `#[byteable(big_endian)]` - Wraps the field in `BigEndian<T>`
281/// - `#[byteable(transparent)]` - Stores the field as its `ByteArray` representation (for nested `Byteable` types)
282/// - No attribute - Keeps the field type as-is
283///
284/// # Requirements
285///
286/// - The struct must have named fields (not a tuple struct)
287/// - Fields with endianness attributes must be numeric types that implement `EndianConvert`
288/// - Fields with `transparent` attribute must implement `Byteable`
289/// - The struct should derive `Clone` and `Copy` for convenience
290///
291/// # Examples
292///
293/// ## Basic usage
294///
295/// ```
296/// # #[cfg(feature = "derive")]
297/// use byteable::Byteable;
298///
299/// # #[cfg(feature = "derive")]
300/// #[derive(Clone, Copy, Byteable)]
301/// struct Packet {
302///     id: u8,
303///     #[byteable(little_endian)]
304///     length: u16,
305///     #[byteable(big_endian)]
306///     checksum: u32,
307///     data: [u8; 4],
308/// }
309///
310/// # #[cfg(feature = "derive")]
311/// # fn example() {
312/// let packet = Packet {
313///     id: 42,
314///     length: 1024,
315///     checksum: 0x12345678,
316///     data: [1, 2, 3, 4],
317/// };
318///
319/// // Byteable is automatically implemented
320/// let bytes = packet.to_byte_array();
321/// let restored = Packet::from_byte_array(bytes);
322/// # }
323/// ```
324///
325/// ## Generated code
326///
327/// The above example generates approximately:
328///
329/// ```ignore
330/// #[derive(Clone, Copy, Debug, UnsafeByteableTransmute)]
331/// #[repr(C, packed)]
332/// struct PacketRaw {
333///     id: u8,
334///     length: LittleEndian<u16>,
335///     checksum: BigEndian<u32>,
336///     data: [u8; 4],
337/// }
338///
339/// impl From<Packet> for PacketRaw {
340///     fn from(value: Packet) -> Self {
341///         Self {
342///             id: value.id,
343///             length: value.length.into(),
344///             checksum: value.checksum.into(),
345///             data: value.data,
346///         }
347///     }
348/// }
349///
350/// impl From<PacketRaw> for Packet {
351///     fn from(value: PacketRaw) -> Self {
352///         Self {
353///             id: value.id,
354///             length: value.length.get(),
355///             checksum: value.checksum.get(),
356///             data: value.data,
357///         }
358///     }
359/// }
360///
361/// impl_byteable_via!(Packet => PacketRaw);
362/// ```
363#[proc_macro_derive(Byteable, attributes(byteable))]
364pub fn byteable_delegate_derive_macro(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
365    // Find the byteable crate name
366    let found_crate = crate_name("byteable").expect("byteable is present in `Cargo.toml`");
367
368    // Determine the correct path to the byteable crate
369    let byteable_crate = match found_crate {
370        FoundCrate::Itself => quote!(::byteable),
371        FoundCrate::Name(name) => {
372            let ident = Ident::new(&name, Span::call_site());
373            quote!( #ident )
374        }
375    };
376
377    // Parse the input
378    let input: DeriveInput = parse_macro_input!(input);
379    let original_name = &input.ident;
380
381    // Create the raw struct name by appending "Raw"
382    let raw_name = Ident::new(
383        &format!("__byteable_raw_{}", original_name),
384        original_name.span(),
385    );
386
387    // Extract fields from the struct and determine if it's a tuple struct or named struct
388    let (fields, is_tuple_struct) = match &input.data {
389        Data::Struct(data) => match &data.fields {
390            Fields::Named(fields) => (&fields.named, false),
391            Fields::Unnamed(fields) => (&fields.unnamed, true),
392            Fields::Unit => panic!("Byteable does not support unit structs"),
393        },
394        _ => panic!("Byteable only supports structs"),
395    };
396
397    // Process each field to determine its type in the raw struct and conversion logic
398    let mut raw_fields = Vec::new();
399    let mut raw_field_types = Vec::new(); // Track raw field types for ValidBytecastMarker
400    let mut from_original_conversions = Vec::new();
401    let mut from_raw_conversions = Vec::new();
402
403    for (index, field) in fields.iter().enumerate() {
404        let field_type = &field.ty;
405
406        // Check for byteable attributes
407        let mut attribute_type = None;
408        for attr in &field.attrs {
409            if attr.path().is_ident("byteable") {
410                if let Meta::List(meta_list) = &attr.meta {
411                    let tokens = &meta_list.tokens;
412                    let tokens_str = tokens.to_string();
413                    if tokens_str == "little_endian" {
414                        attribute_type = Some("little");
415                    } else if tokens_str == "big_endian" {
416                        attribute_type = Some("big");
417                    } else if tokens_str == "transparent" {
418                        attribute_type = Some("transparent");
419                    } else {
420                        panic!(
421                            "Unknown byteable attribute: {}. Valid attributes are: little_endian, big_endian, transparent",
422                            tokens_str
423                        );
424                    }
425                }
426            }
427        }
428
429        // For tuple structs, use index-based access; for named structs, use field names
430        if is_tuple_struct {
431            let idx = syn::Index::from(index);
432
433            match attribute_type {
434                Some("little") => {
435                    let raw_ty = quote! { #byteable_crate::LittleEndian<#field_type> };
436                    raw_fields.push(raw_ty.clone());
437                    raw_field_types.push(raw_ty);
438                    from_original_conversions.push(quote! {
439                        value.#idx.into()
440                    });
441                    from_raw_conversions.push(quote! {
442                        value.#idx.get()
443                    });
444                }
445                Some("big") => {
446                    let raw_ty = quote! { #byteable_crate::BigEndian<#field_type> };
447                    raw_fields.push(raw_ty.clone());
448                    raw_field_types.push(raw_ty);
449                    from_original_conversions.push(quote! {
450                        value.#idx.into()
451                    });
452                    from_raw_conversions.push(quote! {
453                        value.#idx.get()
454                    });
455                }
456                Some("transparent") => {
457                    // Use the ByteableRaw::Raw type directly for better type safety
458                    let raw_ty = quote! { <#field_type as #byteable_crate::ByteableRaw>::Raw };
459                    raw_fields.push(raw_ty.clone());
460                    raw_field_types.push(raw_ty);
461                    from_original_conversions.push(quote! {
462                        value.#idx.into()
463                    });
464                    from_raw_conversions.push(quote! {
465                        value.#idx.into()
466                    });
467                }
468                _ => {
469                    let raw_ty = quote! { #field_type };
470                    raw_fields.push(raw_ty.clone());
471                    raw_field_types.push(raw_ty);
472                    from_original_conversions.push(quote! {
473                        value.#idx
474                    });
475                    from_raw_conversions.push(quote! {
476                        value.#idx
477                    });
478                }
479            }
480        } else {
481            // Named struct
482            let field_name = field.ident.as_ref().unwrap();
483
484            match attribute_type {
485                Some("little") => {
486                    let raw_ty = quote! { #byteable_crate::LittleEndian<#field_type> };
487                    raw_fields.push(quote! {
488                        #field_name: #raw_ty
489                    });
490                    raw_field_types.push(raw_ty);
491                    from_original_conversions.push(quote! {
492                        #field_name: value.#field_name.into()
493                    });
494                    from_raw_conversions.push(quote! {
495                        #field_name: value.#field_name.get()
496                    });
497                }
498                Some("big") => {
499                    let raw_ty = quote! { #byteable_crate::BigEndian<#field_type> };
500                    raw_fields.push(quote! {
501                        #field_name: #raw_ty
502                    });
503                    raw_field_types.push(raw_ty);
504                    from_original_conversions.push(quote! {
505                        #field_name: value.#field_name.into()
506                    });
507                    from_raw_conversions.push(quote! {
508                        #field_name: value.#field_name.get()
509                    });
510                }
511                Some("transparent") => {
512                    // Use the ByteableRaw::Raw type directly for better type safety
513                    let raw_ty = quote! { <#field_type as #byteable_crate::ByteableRaw>::Raw };
514                    raw_fields.push(quote! {
515                        #field_name: #raw_ty
516                    });
517                    raw_field_types.push(raw_ty);
518                    from_original_conversions.push(quote! {
519                        #field_name: value.#field_name.into()
520                    });
521                    from_raw_conversions.push(quote! {
522                        #field_name: value.#field_name.into()
523                    });
524                }
525                _ => {
526                    let raw_ty = quote! { #field_type };
527                    raw_fields.push(quote! {
528                        #field_name: #raw_ty
529                    });
530                    raw_field_types.push(raw_ty);
531                    from_original_conversions.push(quote! {
532                        #field_name: value.#field_name
533                    });
534                    from_raw_conversions.push(quote! {
535                        #field_name: value.#field_name
536                    });
537                }
538            }
539        }
540    }
541
542    // Generate the output code
543    let output = if is_tuple_struct {
544        quote! {
545            // Generate the raw struct (tuple struct)
546            #[derive(Clone, Copy, Debug)]
547            #[repr(C, packed)]
548            #[doc(hidden)]
549            struct #raw_name(#(#raw_fields),*);
550
551            // Automatic ValidBytecastMarker impl for the raw struct
552            // This is safe because all fields implement ValidBytecastMarker
553            unsafe impl #byteable_crate::ValidBytecastMarker for #raw_name
554            where
555                #(#raw_field_types: #byteable_crate::ValidBytecastMarker),*
556            {}
557
558            #byteable_crate::unsafe_byteable_transmute!(#raw_name);
559
560            // From original to raw
561            impl From<#original_name> for #raw_name {
562                fn from(value: #original_name) -> Self {
563                    Self(#(#from_original_conversions),*)
564                }
565            }
566
567            // From raw to original
568            impl From<#raw_name> for #original_name {
569                fn from(value: #raw_name) -> Self {
570                    Self(#(#from_raw_conversions),*)
571                }
572            }
573
574            // Implement Byteable for the original struct via the raw struct
575            #byteable_crate::impl_byteable_via!(#original_name => #raw_name);
576
577            // Implement ByteableRaw to expose the raw type
578            impl #byteable_crate::ByteableRaw for #original_name {
579                type Raw = #raw_name;
580            }
581        }
582    } else {
583        quote! {
584            #[derive(Clone, Copy, Debug)]
585            #[repr(C, packed)]
586            #[doc(hidden)]
587            pub struct #raw_name {
588                #(#raw_fields),*
589            }
590
591            // Automatic ValidBytecastMarker impl for the raw struct
592            // This is safe because all fields implement ValidBytecastMarker
593            unsafe impl #byteable_crate::ValidBytecastMarker for #raw_name
594            where
595                #(#raw_field_types: #byteable_crate::ValidBytecastMarker),*
596            {}
597
598            #byteable_crate::unsafe_byteable_transmute!(#raw_name);
599
600            // From original to raw
601            impl From<#original_name> for #raw_name {
602                fn from(value: #original_name) -> Self {
603                    Self {
604                        #(#from_original_conversions),*
605                    }
606                }
607            }
608
609            impl From<#raw_name> for #original_name {
610                fn from(value: #raw_name) -> Self {
611                    Self {
612                        #(#from_raw_conversions),*
613                    }
614                }
615            }
616
617            #byteable_crate::impl_byteable_via!(#original_name => #raw_name);
618
619            // Implement ByteableRaw to expose the raw type
620            impl #byteable_crate::ByteableRaw for #original_name {
621                type Raw = #raw_name;
622            }
623        }
624    };
625    output.into()
626}