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}