RepoMicrosoftMicrosoftpublished Apr 8, 2026seen Jun 26

microsoft/vercode

Rust

Open original ↗

Captured source

source ↗
published Apr 8, 2026seen Jun 26captured 6dhttp 200method plain

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 bool

Zero-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 Vercode on 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
  • NonZeroXXX types
  • f32 and f64
  • bool, char, and ()
  • String and Uuid

Containers where T, K, V are also supported types

  • Option
  • [T; N]
  • Vec
  • HashMap and HashSet

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.