Skip to main content

Crate msgpacker

Crate msgpacker 

Source
Expand description

§MessagePacker - a no-std msgpack implementation

crates.io Documentation License

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 msgpacker::{MsgPacker, Packable as _, Unpackable as _};
use std::collections::HashMap;

// boilerplate derives - those aren't required
#[derive(Debug, PartialEq, Eq)]
// this convenience derive macro will implement `Packable` and `Unpackable`
#[derive(MsgPacker)]
pub struct City {
    name: String,

    // The traits are implemented for stdlib collections. If you have a custom map, you can use the
    // directive `#[msgpacker(map)]` so the traits will be automatically implemented through the
    // iterators of the map.
    inhabitants_per_street: HashMap<String, u64>,

    // This is also automatically implemented. The manual implementation is via `#[msgpacker(array)]`.
    zones: Vec<String>,

    // Binary serialization is specialized in MessagePack. We can use the directive here.
    #[msgpacker(binary)]
    metadata: Vec<u8>,
}

// create an instance of a city.
let city = City {
    name: "Kuala Lumpur".to_string(),
    inhabitants_per_street: HashMap::from([
        ("Street 1".to_string(), 10),
        ("Street 2".to_string(), 20),
    ]),
    zones: vec!["Zone 1".to_string(), "Zone 2".to_string()],
    metadata: b"foo".to_vec(),
};

// serialize the city into bytes
let buf = city.pack_to_vec();

// deserialize the city and assert correctness
let deserialized = City::unpack(&buf).unwrap();
assert_eq!(city, deserialized);

We also support borrowed deserialization:

use msgpacker::{MsgPackerBorrowed, Packable as _, UnpackableBorrowed as _};

#[derive(Debug, MsgPackerBorrowed, PartialEq, Eq)]
pub struct City<'a> {
    name: &'a str,
}

// create an instance of a city.
let city = City {
    name: "Kuala Lumpur"
};

let buf = city.pack_to_vec();

let deserialized = City::unpack(&buf).unwrap();
assert_eq!(city, deserialized);

§Features

  • derive: Enables MsgPacker derive 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 Packable and Unpackable for std collections.
  • serde: Adds support for serde

§Serde

Version 0.5.0 introduces serde support.

use msgpacker::serde;
use serde_json::{json, Value};

let val = serde_json::json!({"foo": "bar"});
let ser = serde::to_vec(&val);
let des: Value = serde::from_slice(&ser).unwrap();

assert_eq!(val, des);

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 Vec for 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 Vec with 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<_> and String (not supported by rmp-serde).
instances countmsgpackermsgpacker serdermpszerompk
pack alloc 167.629 ns251.75 ns322.38 ns277.77 ns
pack alloc 10333.36 ns764.16 ns1.0981 µs799.47 ns
pack alloc 1003.6478 µs4.2132 µs6.9683 µs4.4160 µs
pack alloc 100066.081 µs67.055 µs105.28 µs66.911 µs
pack prealloc 149.170 ns54.865 ns94.714 ns46.801 ns
pack prealloc 10293.30 ns367.28 ns647.95 ns264.44 ns
pack prealloc 1002.9884 µs3.5127 µs6.2381 µs2.8508 µs
pack prealloc 100056.887 µs65.492 µs102.09 µs52.176 µs
unpack owned 1137.10 ns92.482 ns251.77 ns138.14 ns
unpack owned 101.2414 µs1.0115 µs2.2232 µs992.32 ns
unpack owned 10011.820 µs9.2322 µs22.659 µs9.9914 µs
unpack owned 1000231.64 µs205.06 µs333.20 µs221.33 µs
unpack borrowed 196.822 ns57.465 ns67.606 ns
unpack borrowed 10868.06 ns662.06 ns572.57 ns
unpack borrowed 1007.9413 µs5.8026 µs5.4400 µs
unpack borrowed 1000127.29 µs101.97 µs102.96 µs

To run the benchmarks:

RUSTFLAGS="-C target-cpu=native" cargo bench --profile bench

§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 msgpacker::serde;
use serde_json::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: &[u8] = &[146, 0, 203, 66, 120, 167, 66, 234, 244, 144, 0];

// Deserialize into a dynamic Value — works for any valid MessagePack payload
let value: Value = serde::from_slice(bytes).unwrap();
let items = value.as_array().unwrap();

// Each element retains its original type
assert!(items[0].is_u64());
assert!(items[1].is_f64());

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!

Modules§

serde
serde implementations.

Structs§

Encoder
An optimized encoder for dynamic allocation.
EncoderSlice
An optimized encoder for pre-allocated bytes.

Enums§

Error
Deserialization errors for the protocol implementation.
Extension
Custom extension definition as reference to a bytes source.

Traits§

BufMut
A trait for values that provide sequential write access to bytes.
Packable
A packable type.
Unpackable
An unpackable type.
UnpackableBorrowed
A borrowed unpackable type.

Functions§

pack_array
Packs an array into the extendable buffer, returning the amount of written bytes.
pack_array_slice
Packs an array into the extendable buffer, returning the amount of written bytes.
pack_bytes
Packs a byte slice as msgpack binary, returning the amount of written bytes.
pack_bytes_option
Packs Option<&[u8]> as msgpack nil (None) or binary (Some), returning bytes written.
pack_map
Packs a map into the extendable buffer, returning the amount of written bytes.
pack_to_vec
Packs the provided packable value into a vector.
unpack_array
Unpacks an array from the buffer, returning a collectable type and the amount of read bytes.
unpack_array_borrowed
Unpacks an array from the buffer using the borrowed deserialization path.
unpack_array_iter
Unpacks an array from the iterator, returning a collectable type and the amount of read bytes.
unpack_bytes
Unpack bytes on binary format
unpack_bytes_iter
Unpack bytes on binary format using an iterator implementation.
unpack_bytes_option
Unpacks msgpack nil as None, or binary data as Some(Vec<u8>).
unpack_bytes_option_iter
Iterator-based version: unpacks nil as None, binary data as Some(Vec<u8>).
unpack_bytes_option_ref
Unpacks msgpack nil as None, or binary data as Some(&[u8]).
unpack_map
Unpacks a map from the buffer, returning a collectable type and the amount of read bytes.
unpack_map_iter
Unpacks a map from the iterator, returning a collectable type and the amount of read bytes.

Derive Macros§

MsgPacker
MsgPackerBorrowed
MsgUnpackerBorrowed