assert_size_derive/lib.rs
1//! Compile-time type size assertions.
2//!
3//! This crate provides the [`assert_size`] attribute macro for verifying that types
4//! have the expected size in bytes at compile time.
5//!
6//! # Quick Start
7//!
8//! ```
9//! use assert_size_derive::assert_size;
10//!
11//! #[assert_size(2)]
12//! struct MyData {
13//! foo: u8,
14//! bar: u8,
15//! }
16//! ```
17//!
18//! If the size doesn't match, compilation will fail with a clear error message.
19//!
20//! # Use Cases
21//!
22//! - Catching unintended size changes from code refactoring
23//! - Ensuring types meet specific memory layout requirements for FFI or serialization
24//! - Documenting expected type sizes for performance-critical code
25//! - Detecting platform-specific size variations
26
27use proc_macro::TokenStream;
28use quote::quote;
29use syn::{
30 DeriveInput, LitInt, Result, parse::{Parse, ParseStream}, parse_macro_input
31};
32
33struct AssertSizeAttributeArgs {
34 desired_size_in_bytes: usize,
35}
36
37impl Parse for AssertSizeAttributeArgs {
38 fn parse(input: ParseStream) -> Result<Self> {
39 // Parse the input as a single integer literal
40 let lit: LitInt = input.parse()?;
41
42 // Use a base10 conversion to get the integer value
43 let value: usize = lit.base10_parse()?;
44 Ok(AssertSizeAttributeArgs { desired_size_in_bytes: value })
45 }
46}
47
48/// A compile-time assertion that verifies a type has the expected size in bytes.
49///
50/// This attribute macro generates a compile-time check using `std::mem::size_of` to ensure
51/// the annotated type has exactly the specified size. If the size doesn't match, compilation
52/// will fail with a clear error message.
53///
54/// # Parameters
55///
56/// * A single integer literal representing the expected size in bytes
57///
58/// # Use Cases
59///
60/// - Catching unintended size changes from code refactoring
61/// - Ensuring types meet specific memory layout requirements (e.g., for FFI or serialization)
62/// - Documenting expected type sizes for performance-critical code
63/// - Detecting platform-specific size variations
64///
65/// # How It Works
66///
67/// The macro generates a const assertion that compares the actual size (via `core::mem::size_of`)
68/// with the expected size. The type definition itself is preserved unchanged. The assertion is
69/// evaluated at compile time, so there is zero runtime overhead. Works in both `std` and `no_std`
70/// environments.
71///
72/// # Examples
73///
74/// ```
75/// use assert_size_derive::assert_size;
76///
77/// #[assert_size(2)]
78/// struct MyData {
79/// foo: u8,
80/// bar: u8,
81/// }
82///
83/// #[assert_size(16)]
84/// enum MyEnum {
85/// Variant1(u64),
86/// Variant2(u32),
87/// }
88/// ```
89///
90/// ## Compile-Time Failure Example
91///
92/// ```compile_fail
93/// use assert_size_derive::assert_size;
94///
95/// // This will fail to compile because the actual size is 2 bytes, not 1
96/// #[assert_size(1)]
97/// struct TooSmall {
98/// a: u8,
99/// b: u8,
100/// }
101/// ```
102///
103/// # Compatibility
104///
105/// Works with any type definition: structs, enums, and unions.
106#[proc_macro_attribute]
107pub fn assert_size(attr: TokenStream, item: TokenStream) -> TokenStream {
108 let args = parse_macro_input!(attr as AssertSizeAttributeArgs);
109
110 let input = parse_macro_input!(item as DeriveInput);
111
112 let desired_size_in_bytes = args.desired_size_in_bytes;
113 let type_name = &input.ident;
114
115 let generated_test_code = quote! {
116 #[allow(unknown_lints, clippy::eq_op)]
117 const _: () = assert!(#desired_size_in_bytes == ::core::mem::size_of::<#type_name>());
118
119 #input
120 };
121
122 generated_test_code.into()
123}