microsoft/vercode
Rust
Captured source
source ↗microsoft/vercode
Description: Low overhead versionable serialization of Rust data structures
Language: Rust
License: MIT
Stars: 4
Forks: 3
Open issues: 1
Created: 2026-04-08T16:49:32Z
Pushed: 2026-10-01T16:25:08Z
Default branch: main
Fork: no
Archived: no
README:
Vercode - Version based encoding and decoding
Minimal overhead backward and forward compatible serialization for evolving data structures.
Vercode is a Rust serialization library designed for systems that need to handle multiple versions of the same data structure simultaneously—like long-running processes, distributed systems, or protocols that must maintain backward compatibility.
Why Vercode?
Traditional serialization libraries like serde force you to choose: either break compatibility when you add fields, maintain multiple struct definitions, or use versionable formats that add runtime overhead.
Alternatives like flatbuffers or protocol buffers require an external IDL and code generation. The resulting generated wrappers are not idiomatic Rust leading to additional hand wrapping, copying, or non-idiomatic Rust.
Vercode has lower runtime overhead than bincode (and slightly larger encoded size) while allowing for adding new fields to structs and enum variants while maintaining full backwards and forward compatibility. Specifically future code can deserialize messages from old serializers, getting Default::default() for the new fields. Past code can deserialize message from future serializers, skipping the new data it does not understand.
The revision crate addresses similar versioning needs but only supports new code reading older revisions. Vercode provides bidirectional compatibility, allowing old code to read new revisions.
Quick Start
Add to your Cargo.toml:
[dependencies]
vercode = { version = "0.5" }Example: Evolving a Protocol
Here's a complete example showing structs, enums, and version evolution:
use vercode::{Vercode, serialize, deserialize};
// Enum with versioned fields in variants
#[derive(Vercode, Debug, PartialEq)]
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
// Added in version 1: authenticated messages
#[version(1)]
Auth {
token: String,
// expanded with addition field in version 2
#[version(2)]
expires_at: u64,
},
}
// Struct that evolved over three versions
#[derive(Vercode, Default, Debug, PartialEq)]
#[vercode(max_version = 2)]
struct User {
// Version 0 fields (always present, no Default required)
id: u32,
name: String,
// Version 1 field (added later, requires Default)
#[version(1)]
email: String,
#[version(1)]
last_message: Message,
// Version 2 fields (even newer)
#[version(2)]
verified: bool,
}
// Create a fully versioned user
let user = User {
id: 42,
name: "Alice".into(),
email: "alice@example.com".into(),
verified: true,
last_message: Message::Move { x: 100, y: 200 },
};
let mut buf = vec![0u8; 1024];
let serialized = serialize(&user, &mut buf);
// Deserialize - automatically handles version differences
let decoded: User = deserialize(serialized);
assert_eq!(decoded, user);
// Simulate old version (v0) reading new data
let old_data = vercode::serialize_version(&user, 0, &mut buf);
let old_user: User = deserialize(old_data);
// Fields from v1+ will be Default::default()
assert_eq!(old_user.id, 42);
assert_eq!(old_user.name, "Alice");
assert_eq!(old_user.email, ""); // Default for String
assert_eq!(old_user.verified, false); // Default for boolZero-Overhead Newtypes
For newtypes that should serialize identically to their inner type:
use vercode::VercodeTransparent; #[derive(VercodeTransparent)] struct UserId(u64); // Serializes as just a u64, no wrapper overhead
Integer enums with default fallback
For fieldless enums whose unknown wire codes should map to Default::default(), derive VercodeEnumU8 or VercodeEnumU16 instead of Vercode. These implement VerCodable and the U8Enum or U16Enum marker trait, respectively, delegating to the existing integer codec.
use num_enum::{IntoPrimitive, TryFromPrimitive};
use vercode::{VercodeEnumU8, deserialize, serialize_to_vec};
#[derive(Copy, Clone, Debug, Default, PartialEq, IntoPrimitive, TryFromPrimitive, VercodeEnumU8)]
#[repr(u8)]
enum Mode {
Normal = 0,
#[default]
Unknown = 255,
}
assert_eq!(serialize_to_vec(&Mode::Normal), vec![0]);
assert_eq!(deserialize::(&[42]).unwrap(), Mode::Unknown);Add num_enum = "0.7" to your dependencies for the conversion derives, or implement TryFrom and Into yourself. For VercodeEnumU16, use #[repr(u16)] and the corresponding u16 conversions. Both require Copy and Default.
- Use explicit, stable codes and choose a safe default.
#[default]selects
the fallback; the variant does not have to be named Unknown.
- Unknown values on the wire are discarded when deserializing. The enum value will
be the result of Default::default() for the enum type.
- Truncated input returns
InvalidEncoding, not the default. - Encoding is one byte (
VercodeEnumU8) or two little-endian bytes (VercodeEnumU16),
without an enum length prefix. It is not wire-compatible with derived Vercode enums.
- Do not also derive
Vercodeon these enums. Implementing just the marker trait
does not enable serialization; use the matching derive. Separate blanket implementations for the two markers would overlap under Rust's coherence rules.
See [vercode/examples/num_enum.rs](vercode/examples/num_enum.rs) for a parent struct read by an older enum version. Run it with:
cargo run -p vercode --example num_enum
Format
Vercode uses a length-prefixed binary format:
1. Structs: [4-byte length][field₀][field₁]...[fieldₙ] 2. Enums: [4-byte length][2-byte discriminant][variant fields...] 3. Primitives: Direct byte representation (little-endian)
Fields are ordered by version number (v0, then v1, then v2, etc.), and then source order.
Supported Types
Native types implementing VerCodable:
- All integer types
NonZeroXXXtypesf32andf64bool,char, and()StringandUuid
Containers where T, K, V are also supported types
Option[T; N]VecHashMapandHashSet
Nested structs (via Vercode derive attribute)
Enums with both unit and data-carrying variants (via Vercode derive attribute)
Tuples of size 1 to 10 consisting of elements that are in this list...
Excerpt shown — open the source for the full document.