/custom-macro

This project implements custom Serialize and Deserialize traits with procedural macros that automatically generate serialization code for structs containing integer fields.

Primary LanguageRust

Custom Serialize/Deserialize Macro

A Rust procedural macro implementation for automatic serialization and deserialization of structs containing integer fields.

Project Structure

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

Overview

This project implements custom Serialize and Deserialize traits with procedural macros that automatically generate serialization code for structs containing integer fields.

Features

  • 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

Components

1. Traits (serialize_macro_traits)

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;

2. Procedural Macros (serialize_macro)

Two derive macros:

  • #[derive(SerializeNumberStruct)] - Generates Serialize implementation
  • #[derive(DeserializeNumberStruct)] - Generates Deserialize implementation

How It Works

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().

Usage

1. Define Your Struct

use serialize_macro::{SerializeNumberStruct, DeserializeNumberStruct};
use serialize_macro_traits::{Serialize, Deserialize};

#[derive(SerializeNumberStruct, DeserializeNumberStruct)]
struct MyStruct {
    field1: i32,
    field2: i32,
    field3: i32,
}

2. Serialize Data

let my_data = MyStruct {
    field1: 42,
    field2: 100,
    field3: -50,
};

let bytes = my_data.serialize();
println!("Serialized: {:?}", bytes);

3. Deserialize Data

match MyStruct::deserialize(&bytes) {
    Ok(restored) => println!("Deserialized: {:?}", restored),
    Err(e) => println!("Error: {:?}", e),
}

Generated Code Example

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
    }
}

Limitations

  • 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

Building and Running

Build the Project

# Build all components
cargo build

# Build specific crate
cd serialize_macro
cargo build

Run the Example

cd app
cargo run

Expected Output

Original: Swap { a: 5, b: 10 }
Serialized: [0, 0, 0, 5, 0, 0, 0, 10]
Deserialized: Ok(Swap { a: 5, b: 10 })

Technical Details

Macro Implementation

The macros use:

  • syn crate for parsing Rust syntax
  • quote crate for generating code
  • proc_macro for the macro interface

Serialization Format

  • Byte Order: Big-endian (network byte order)
  • Field Order: Fields are serialized in declaration order
  • Size: Each i32 field takes exactly 4 bytes

Error Handling

Deserialization can fail if:

  • Input buffer is too short
  • Byte slice cannot be converted to array

Dependencies

serialize_macro

[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["full"] }

[lib]
proc-macro = true

serialize_macro_traits

# No external dependencies

app

[dependencies]
serialize_macro = { path = "../serialize_macro" }
serialize_macro_traits = { path = "../serialize_macro_traits" }

Contributing

To extend this macro:

  1. Add Type Support: Modify field handling to support other integer types
  2. Add Validation: Implement field type checking
  3. Add Features: Support for enums, nested structs, or different encodings
  4. Improve Errors: Add more descriptive error messages

License

This project is for educational purposes demonstrating Rust procedural macro implementation.