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 file
This 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)]
- GeneratesSerialize
implementation#[derive(DeserializeNumberStruct)]
- GeneratesDeserialize
implementation
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
i32
fields - 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 build
cd app
cargo run
Original: Swap { a: 5, b: 10 }
Serialized: [0, 0, 0, 5, 0, 0, 0, 10]
Deserialized: Ok(Swap { a: 5, b: 10 })
The macros use:
syn
crate for parsing Rust syntaxquote
crate for generating codeproc_macro
for the macro interface
- Byte Order: Big-endian (network byte order)
- Field Order: Fields are serialized in declaration order
- Size: Each
i32
field 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.