afastdata
English | 中文
A high-performance Rust binary serialization/deserialization framework that automatically generates serialization code for custom types via derive macros.
Features
- Zero-config derive macros —
#[derive(AFastSerialize, AFastDeserialize)]in one line - Rich type support — Primitives,
String,Vec<T>,Option<T>,[T; N],Box<T>, tuples,HashMap,HashSet,BTreeMap,BTreeSet, nested structs, enums - Generic support — Automatically adds trait bounds for generic parameters
- Configurable length prefix — Default
u32(max 4GB), switchable tou64via feature flag - Configurable enum tags — Default
u8, switchable tou16oru32via feature flags - Configurable tuple support — Default max 16 elements, switchable to 8 or 32 via feature flags
- Uniform little-endian — All multi-byte data uses little-endian encoding
- Zero runtime dependencies — No third-party dependencies at runtime
Quick Start
Installation
Add the dependency to your Cargo.toml:
[]
= "0.0.8"
For u64 length prefix or custom enum tag type:
[]
= { = "0.0.8", = ["len-u64", "tag-u16"] }
Basic Usage
use ;
Enum Example
use ;
Generic Struct
use ;
Validation
You can add validation rules to struct fields and enum variant fields using the #[afast(...)] attribute.
Struct Validation Example
use ;
Enum Variant Validation Example
Validation rules also work on named-field and tuple enum variants:
use ;
Validate Attribute
Validation rules apply to both struct fields and enum variant fields (named and tuple variants):
skiporskip("default_fn")— Always skip this field during serialization and deserialization. On deserialize, fill with default value (call the given function, orDefault::default())skip_with("marker")orskip_with("marker", "default_fn")— Conditionally skip. When the marker passed toto_bytes_with/from_bytes_withmatches this field's marker, the field is skipped. Otherwise it is serialized/deserialized normally. The marker automatically propagates to nested types — if a struct field contains another struct withskip_withfields, those inner fields also respond to the same markergt(value, code, message): field value must be greater thanvalue(supports integer and float literals), otherwise returnValidateError. Applicable to numeric types andOption<numeric>(Nonepasses validation)gte(value, code, message): field value must be greater than or equal tovalue, otherwise returnValidateError. Applicable to numeric types andOption<numeric>lt(value, code, message): field value must be less thanvalue, otherwise returnValidateError. Applicable to numeric types andOption<numeric>lte(value, code, message): field value must be less than or equal tovalue, otherwise returnValidateError. Applicable to numeric types andOption<numeric>len(min, max, code, message): field length must be betweenminandmax, applicable toString,Vec<T>,[T; N], orOption<T>wrapping these types (Nonepasses validation). Pass-1forminormaxto leave that bound unrestricted, e.g.len(3, -1, ...)means min length 3 with no max limitof([v1, v2, ...], code, message): field value must be one of[v1, v2, ...], otherwise returnValidateErrorfunc(name)— Call external function for validation. Signature:fn(value: &T, field: &str) -> Result<(), ValidateError>
Type checking: Validation rules check field type compatibility at compile time. For example,
gt/gte/lt/lteonly work on numeric types orOption<numeric>, andlenonly works on string and collection types. Incompatible combinations produce a compile error.
Conditional Serialization (skip_with)
skip_with enables conditional field skipping based on a marker string, useful when the same type needs different serialization strategies in different contexts:
use ;
to_bytes_with(marker) and from_bytes_with(data, marker) accept a marker string:
- Marker matches the field's
skip_withtag → serialize skips the field; deserialize fills with default value or custom function - Marker does not match → behavior is identical to
to_bytes()/from_bytes() - Nested propagation — when serializing/deserializing a struct with
_withmethods, the marker automatically propagates to all nested types. If an inner struct also hasskip_withfields, they will respond to the same marker - For primitive types (
i32,String,Vec<T>, etc.), the_withmethods default to calling the regular methods
Nested Type Example
skip_with works automatically across nested structs — no special configuration needed:
use ;
Field Name Safety
All internal variables in generated code are prefixed with __afast_ to avoid conflicts with your field names. You can safely use field names like data, offset, or bytes without any issues:
Supported Types
| Type | Serialization | Size |
|---|---|---|
i8, u8 |
little-endian | 1 byte |
i16, u16 |
little-endian | 2 bytes |
i32, u32 |
little-endian | 4 bytes |
i64, u64 |
little-endian | 8 bytes |
usize |
u64 little-endian (cross-platform) | 8 bytes |
i128, u128 |
little-endian | 16 bytes |
f32 |
IEEE 754 little-endian | 4 bytes |
f64 |
IEEE 754 little-endian | 8 bytes |
bool |
0x00=false, 0x01=true |
1 byte |
String |
LenInt prefix + UTF-8 bytes | Variable |
&str |
LenInt prefix + UTF-8 bytes (serialize only) | Variable |
Vec<T> |
LenInt element count + element-wise encoding | Variable |
Option<T> |
1-byte tag + data (only when Some) | Variable |
[T; N] |
Element-wise encoding, no length prefix | Fixed |
(A, B, ...) |
Element-wise encoding, no length prefix | Fixed/Variable |
Box<T> |
Same as T |
Same as T |
HashMap<K, V> |
LenInt entry count + key-value pair encoding | Variable |
HashSet<T> |
LenInt element count + element-wise encoding | Variable |
BTreeMap<K, V> |
LenInt entry count + key-value pair encoding | Variable |
BTreeSet<T> |
LenInt element count + element-wise encoding | Variable |
| Struct | Field-by-field encoding, no extra prefix | Variable |
| Enum | Tag(u8/u16/u32) variant index + variant field data | Variable |
Encoding Format
Struct
All fields are serialized in declaration order with no additional prefix:
[field1 bytes][field2 bytes][field3 bytes]...
Enum
Writes a variant index (Tag type, default u8, switchable to u16 or u32 via feature flags, starting from 0, incrementing by declaration order), followed by the variant's field data:
[Tag variant_index][field1 bytes][field2 bytes]...
Unit variants only write the index, with no field data.
Length Prefix
Variable-length types like String and Vec<T> use LenInt as the length prefix:
- Default:
u32little-endian (4 bytes, max ~4GB) - With
len-u64feature:u64little-endian (8 bytes)
Feature Flags
| Feature | Description | Default |
|---|---|---|
len-u64 |
Switch the length prefix from u32 to u64 |
No |
tag-u8 |
Enum variant tag uses u8 (1 byte, max 256 variants) |
Yes |
tag-u16 |
Enum variant tag uses u16 (2 bytes, max 65536 variants) |
No |
tag-u32 |
Enum variant tag uses u32 (4 bytes, max ~4.2 billion variants) |
No |
tuple-8 |
Tuple support up to 8 elements | No |
tuple-16 |
Tuple support up to 16 elements | Yes |
tuple-32 |
Tuple support up to 32 elements | No |
Project Structure
afastdata/
├── Cargo.toml # Workspace configuration
├── README.md # English documentation (this file)
├── README_CN.md # 中文文档
├── afastdata/ # Core library + unified entry crate
│ ├── Cargo.toml # Contains `len-u64`, `tag-*`, `tuple-*` features
│ ├── src/lib.rs # Trait definitions + primitive type implementations + re-exports derive macros
│ └── tests/
│ ├── derive_tests.rs # Derive macro integration tests
│ └── primitive_tests.rs # Primitive type serialization tests
└── afastdata-macro/ # Proc-macro library
├── Cargo.toml
└── src/lib.rs # AFastSerialize / AFastDeserialize derive macros
Running the Example
Running Tests
License
MIT