Information
Revision is a framework for revision-tolerant serialization and deserialization with support for schema evolution over time. It allows for easy revisioning of structs and enums for data storage requirements which need to support backwards compatibility, but where the design of the data structures evolve over time. Revision enables data that was serialized at older revisions to be seamlessly deserialized and converted into the latest data structures. It uses bincode for serialization and deserialization.
The Revisioned trait is automatically implemented for the following primitives: u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64, char, String, Vec<T>, Arrays up to 32 elements, Option<T>, Box<T>, Bound<T>, Wrapping<T>, Reverse<T>, (A, B), (A, B, C), (A, B, C, D), (A, B, C, D, E), Duration, HashMap<K, V>, BTreeMap<K, V>, HashSet<T>, BTreeSet<T>, BinaryHeap<T>, Result<T, E>, Cow<'_, T>, Decimal, regex::Regex, uuid::Uuid, chrono::Duration, chrono::DateTime<Utc>, geo::Point, geo::LineString geo::Polygon, geo::MultiPoint, geo::MultiLineString, geo::MultiPolygon, and ordered_float::NotNan.
Feature Flags
Revision supports the following feature flags:
specialised-vectors(default): Enables specialised implementations for certain vector types that provide serialisation and deserialisation performance improvements.fixed-width-encoding: Uses fixed-width encoding for integers instead of variable-length encoding. By default, Revision uses variable-length encoding which is more space-efficient for small values but has overhead for large values. With this feature enabled, all integers use their full size (2 bytes foru16/i16, 4 bytes foru32/i32, 8 bytes foru64/i64, 16 bytes foru128/i128), providing predictable serialization sizes, and improved serialisation and deserialisation performance.skip(disabled by default): EnablesSkipRevisioned/SkipCheckRevisioned,skip_slice/skip_check_slice(plusskip_reader/skip_check_readeraliases), slice fast paths, and matching derive output (#[revisioned(..., skip = false)]opts out per type). Library crates should forwardskip = ["revision/skip"]and documentfeatures = ["skip"]for dependents; see Skipping encoded values below.
Integer Encoding Trade-offs
Variable-length encoding (default):
- Small values (0-250) use only 1 byte
- More compact for typical workloads with mostly small values
- Variable serialization size based on value magnitude
- Slight overhead for very large values
Fixed-width encoding (fixed-width-encoding feature):
- Predictable, constant serialization size per type
- No branching or size checks during encoding/decoding
- Less compact for small values
- More efficient for workloads with large values
Benchmarking
To compare variable-length vs fixed-width encoding performance:
# Benchmark with default variable-length encoding
# Benchmark with fixed-width encoding
The varint_comparison benchmark tests serialization and deserialization performance across different data distributions (small values, large values, and mixed distributions) for all integer types.
Inspiration
This code takes inspiration from the Versionize library developed for Amazon Firecracker snapshot-restore development previews.
Revision in action
use Error;
use revisioned;
// The test structure is at revision 3.
// The test structure is at revision 3.
Skipping encoded values
Use the skip feature when you handle revisioned bytes but only need to extract certain fields from the binary data - without deserializing full structs or maps into memory.
Extracting one field from a struct
A #[revisioned] struct is laid out as struct revision (u16), then fields in source order. Read only what you need and call SkipRevisioned::skip_revisioned on &mut reader for the rest (or use skip_slice::<T> to skip a whole nested value in one go when you have a sub-slice).
use ;
let row = Row ;
let bytes = to_vec.unwrap;
assert_eq!;
Extracting one entry from a BTreeMap
Maps are encoded as length (usize), then key / value pairs in sorted key order. Typical pattern: deserialize each key, compare, deserialize the value you care about, otherwise skip the value with the appropriate skip_revisioned call.
use ;
use BTreeMap;
let cfg = Config ;
let bytes = to_vec.unwrap;
assert_eq!;
For map values that are themselves #[revisioned] enums or structs, deserialize the discriminant / nested revision as you would when fully deserializing, and call MyValue::skip_revisioned on entries you discard (see benches/skip_mixed_btreemap_nested.rs).
Use skip_check_* when you want validation that matches stricter deserialize checks (e.g. UTF-8 for String). Disable skip for a type with #[revisioned(revision = N, skip = false)].