MessagePacker - a no-std msgpack implementation
The protocol specification can be found here.
This crate targets simplicity and performance. It extends bytes BufMut, for increased interoperability.
The library is designed with production in mind, and will return auxiliary, useful runtime data such as buffer consumption bytes count.
Example
use ;
use HashMap;
// boilerplate derives - those aren't required
// this convenience derive macro will implement `Packable` and `Unpackable`
// create an instance of a city.
let city = City ;
// serialize the city into bytes
let buf = city.pack_to_vec;
// deserialize the city and assert correctness
let deserialized = unpack.unwrap;
assert_eq!;
We also support borrowed deserialization:
use ;
// create an instance of a city.
let city = City ;
let buf = city.pack_to_vec;
let deserialized = unpack.unwrap;
assert_eq!;
Features
- derive: Enables
MsgPackerderive convenience macro. - strict: Will panic if there is a protocol violation of the size of a buffer; the maximum allowed size is
u32::MAX. - assertions: Will panic on runtime if an invalid buffer is provided for prealloc serialization.
- std: Will implement the
PackableandUnpackableforstdcollections. - serde: Adds support for serde
Serde
Version 0.5.0 introduces serde support.
use serde;
use ;
let val = json!;
let ser = to_vec;
let des: Value = from_slice.unwrap;
assert_eq!;
Serde performance is excelling for deserialization, but is slower for serialization.
For more information, refer to Benchmarks.
Benchmarks
Results obtained with AMD EPYC 7402P 24-Core Processor.
The results have their correctness asserted for each benchmark item.
- pack alloc: creates a
Vecfor each serialization. Useful if the user doesn't know the size of his serialized instances. It's the most common case. - pack prealloc: preallocates a
Vecwith the known serialization length, and executes the packing with a mutable, no-alloc slice. - unpack owned: unpacks the structure to an owned type. Useful for complex collections such as
HashMap. - unpack borrowed: unpacks the structure as a borrowed type from the original bytes. Useful for very simple structures with mostly atomic types suck as
Vec<_>andString(not supported by rmp-serde).
| instances count | msgpacker | msgpacker serde | rmps | zerompk |
|---|---|---|---|---|
| pack alloc 1 | 67.629 ns | 251.75 ns | 322.38 ns | 277.77 ns |
| pack alloc 10 | 333.36 ns | 764.16 ns | 1.0981 µs | 799.47 ns |
| pack alloc 100 | 3.6478 µs | 4.2132 µs | 6.9683 µs | 4.4160 µs |
| pack alloc 1000 | 66.081 µs | 67.055 µs | 105.28 µs | 66.911 µs |
| pack prealloc 1 | 49.170 ns | 54.865 ns | 94.714 ns | 46.801 ns |
| pack prealloc 10 | 293.30 ns | 367.28 ns | 647.95 ns | 264.44 ns |
| pack prealloc 100 | 2.9884 µs | 3.5127 µs | 6.2381 µs | 2.8508 µs |
| pack prealloc 1000 | 56.887 µs | 65.492 µs | 102.09 µs | 52.176 µs |
| unpack owned 1 | 137.10 ns | 92.482 ns | 251.77 ns | 138.14 ns |
| unpack owned 10 | 1.2414 µs | 1.0115 µs | 2.2232 µs | 992.32 ns |
| unpack owned 100 | 11.820 µs | 9.2322 µs | 22.659 µs | 9.9914 µs |
| unpack owned 1000 | 231.64 µs | 205.06 µs | 333.20 µs | 221.33 µs |
| unpack borrowed 1 | 96.822 ns | 57.465 ns | † | 67.606 ns |
| unpack borrowed 10 | 868.06 ns | 662.06 ns | † | 572.57 ns |
| unpack borrowed 100 | 7.9413 µs | 5.8026 µs | † | 5.4400 µs |
| unpack borrowed 1000 | 127.29 µs | 101.97 µs | † | 102.96 µs |
To run the benchmarks:
RUSTFLAGS="-C target-cpu=native"
Non-uniform collections
MessagePack is a language-agnostic format. Dynamically typed languages like Python, JavaScript, and Ruby naturally allow mixed-type collections — for instance, a Python list [0, 1694166331209.0] containing both an integer and a float is perfectly valid. When these values are serialized into MessagePack, the resulting byte stream encodes each element with its own type tag (u64, f64, etc.), producing an array whose elements have heterogeneous types.
Rust's type system does not directly support such collections: a Vec<T> requires a single concrete T. As noted in #18, the native Packable/Unpackable traits cannot deserialize these non-uniform arrays because they rely on a statically known element type at compile time.
The serde feature provides a workaround: deserialize the MessagePack bytes into serde_json::Value, which is a dynamically typed enum that can represent any JSON-compatible value. This will incur performance overhead compared to the native traits, since serde uses a visitor pattern that involves runtime type dispatch and heap allocations for every element.
use serde;
use Value;
// MessagePack bytes encoding a 2-element array: [0_u64, 1694166331209.0_f64]
// This kind of payload is common when receiving data from Python, JS, or other
// dynamically typed languages that don't distinguish collection element types.
let bytes: & = &;
// Deserialize into a dynamic Value — works for any valid MessagePack payload
let value: Value = from_slice.unwrap;
let items = value.as_array.unwrap;
// Each element retains its original type
assert!;
assert!;
If your use case involves only uniform collections (e.g. Vec<u64>), prefer the native Packable/Unpackable traits for zero-overhead deserialization.
Note on Github
Although GitHub offers exceptional CI and hosting services virtually for free, its questionable approach towards user sovereignty and privacy is notable. Consequently, I chose to disengage from their infrastructure.
For more information, check Give Up GitHub!