struct_field_offsets/
lib.rs

1//! This crate provides a `field_offsets` member over `struct` declaration
2//! that adorns the `FieldOffsets` macro.
3
4//! For example, this can be used in FFI scenarios where asserting the offset
5//! of each field among the various languages struct becomes a concern.
6
7//! ```rust
8//! use struct_field_offsets::FieldOffsets;
9//!
10//! // at declaration site
11//! #[derive(FieldOffsets)]
12//! #[repr(C)]
13//! struct Data {
14//!     x: i32,
15//!     y: i32,
16//!     label: [u8;8]
17//! }
18//! 
19//! // in the code
20//! let offsets = Data::field_offsets();
21//! for (name,offset) in offsets {
22//! println!("field {name} offset is {offset}.");
23//! }
24//! // prints:
25//! // > field x offset is 0.
26//! // > field y offset is 4.
27//! // > field label offset is 8.
28//! ```
29//! 
30//! In your Cargo.toml:
31//! ```toml
32//! [dependencies]
33//! struct-field-offsets = "*"
34//! ```
35//!
36extern crate proc_macro;
37use proc_macro::TokenStream;
38use quote::quote;
39use syn::{parse_macro_input, DeriveInput, Data, Fields, Ident};
40
41/// ```rust
42/// use struct_field_offsets::FieldOffsets;
43///
44/// // at declaration site
45/// #[derive(FieldOffsets)]
46/// #[repr(C)]
47/// struct Data {
48///     x: i32,
49///     y: i32,
50///     label: [u8;8]
51/// }
52///
53/// // in the code
54/// let offsets = Data::field_offsets();
55/// for (name,offset) in offsets {
56///     println!("field {name} offset is {offset}.");
57/// }
58/// // prints:
59/// // > field x offset is 0.
60/// // > field y offset is 4.
61/// // > field label offset is 8.
62#[proc_macro_derive(FieldOffsets)]
63pub fn field_offsets_derive(input: TokenStream) -> TokenStream {
64    // Parse the input tokens into a syntax tree
65    let input = parse_macro_input!(input as DeriveInput);
66    let name = input.ident;
67    // Extract the fields from the struct
68    let fields: Vec<Ident> = match input.data {
69        Data::Struct(data) => match data.fields {
70            Fields::Named(fields_named) => {
71                fields_named.named.iter().map(|f| f.ident.clone().unwrap()).collect()
72            }
73            _ => panic!("FieldOffsets can only be used with structs with named fields"),
74        },
75        Data::Enum(_) => panic!("FieldOffsets can only be used with structs"),
76        Data::Union(_) => panic!("FieldOffsets can only be used with structs"),
77    };
78    let offsets = fields.iter().map(|field| {
79        quote! {
80            (stringify!(#field), std::mem::offset_of!(#name, #field))
81        }
82    });
83    let len = offsets.len();
84    let expanded = quote! {
85        impl #name {
86            pub fn field_offsets() -> [(&'static str, usize); #len] {
87                [
88                    #(#offsets),*
89                ]
90            }
91        }
92    };
93    TokenStream::from(expanded)
94}