A Rust procedural macro implementation for automatic serialization and deserialization of structs containing integer fields.
custom-macro/
├── serialize_macro/ # Procedural macro implementation
│ ├── src/lib.rs # Main macro definitions
│ └── Cargo.toml # Macro dependencies
├── serialize_macro_traits/ # Trait definitions
│ ├── src/lib.rs # Serialize/Deserialize traits
│ └── Cargo.toml # Trait dependencies
├── app/ # Example application
│ ├── src/main.rs # Demo usage
│ └── Cargo.toml # App dependencies
└── README.md # This fileThis project implements custom Serialize and Deserialize traits with procedural macros that automatically generate serialization code for structs containing integer fields.
- Automatic Serialization: Converts struct fields to bytes using big-endian encoding
- Automatic Deserialization: Reconstructs structs from byte arrays
- Type Safety: Compile-time code generation ensures type safety
- Error Handling: Built-in error handling for malformed data
Defines the core traits:
pub trait Serialize {
fn serialize(&self) -> Vec<u8>;
}
pub trait Deserialize {
fn deserialize(data: &[u8]) -> Result<Self, Error>
where
Self: Sized;
}
#[derive(Debug)]
pub struct Error;Two derive macros:
#[derive(SerializeNumberStruct)]- GeneratesSerializeimplementation#[derive(DeserializeNumberStruct)]- GeneratesDeserializeimplementation
Serialization: Each integer field is converted to bytes using to_be_bytes() and concatenated.
Deserialization: Bytes are read in 4-byte chunks, converted back to i32 using from_be_bytes().
use serialize_macro::{SerializeNumberStruct, DeserializeNumberStruct};
use serialize_macro_traits::{Serialize, Deserialize};
#[derive(SerializeNumberStruct, DeserializeNumberStruct)]
struct MyStruct {
field1: i32,
field2: i32,
field3: i32,
}let my_data = MyStruct {
field1: 42,
field2: 100,
field3: -50,
};
let bytes = my_data.serialize();
println!("Serialized: {:?}", bytes);match MyStruct::deserialize(&bytes) {
Ok(restored) => println!("Deserialized: {:?}", restored),
Err(e) => println!("Error: {:?}", e),
}For a struct like:
#[derive(SerializeNumberStruct)]
struct Point {
x: i32,
y: i32,
}The macro generates:
impl Serialize for Point {
fn serialize(&self) -> Vec<u8> {
let mut result = Vec::new();
result.extend_from_slice(&self.x.to_be_bytes());
result.extend_from_slice(&self.y.to_be_bytes());
result
}
}- Integer Fields Only: Currently supports only
i32fields - Named Fields Only: Requires structs with named fields (not tuple structs)
- Fixed Size: Each field is assumed to be 4 bytes (i32)
- No Nested Structs: Does not handle nested struct serialization
# Build all components
cargo build
# Build specific crate
cd serialize_macro
cargo buildcd app
cargo runOriginal: Swap { a: 5, b: 10 }
Serialized: [0, 0, 0, 5, 0, 0, 0, 10]
Deserialized: Ok(Swap { a: 5, b: 10 })The macros use:
syncrate for parsing Rust syntaxquotecrate for generating codeproc_macrofor the macro interface
- Byte Order: Big-endian (network byte order)
- Field Order: Fields are serialized in declaration order
- Size: Each
i32field takes exactly 4 bytes
Deserialization can fail if:
- Input buffer is too short
- Byte slice cannot be converted to array
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["full"] }
[lib]
proc-macro = true# No external dependencies[dependencies]
serialize_macro = { path = "../serialize_macro" }
serialize_macro_traits = { path = "../serialize_macro_traits" }To extend this macro:
- Add Type Support: Modify field handling to support other integer types
- Add Validation: Implement field type checking
- Add Features: Support for enums, nested structs, or different encodings
- Improve Errors: Add more descriptive error messages
This project is for educational purposes demonstrating Rust procedural macro implementation.