Expand description
§shmp: A flexible and efficient MessagePack implementation
shmp offers a robust and easy-to-use toolkit for serializing and deserializing
data with the MessagePack format in Rust. It is designed with serde as a first-class
citizen, providing maximum performance and type safety.
§Two Ways to Work with MessagePack
There are two primary approaches to using shmp:
- Direct Serialization with Serde (Recommended): Serialize your Rust structs directly to and from MessagePack bytes. This is the most efficient and idiomatic method.
- Dynamic
PackEnum: Use theshmp::Packenum, which is similar toserde_json::Value, to represent and manipulate MessagePack data when the structure is not known at compile time.
§Quick Start: Using Serde
The most common use case is to derive Serialize and Deserialize on your types
and use the top-level to_vec and from_slice functions.
use serde::{Serialize, Deserialize};
use shmp::{to_vec, from_slice};
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct User {
id: u32,
username: String,
is_active: bool,
}
fn main() -> Result<(), shmp::Error> {
let user = User {
id: 1024,
username: "shmp_user".to_string(),
is_active: true,
};
// Serialize the struct into MessagePack bytes.
let encoded: Vec<u8> = to_vec(&user)?;
// Deserialize the bytes back into a struct.
let decoded: User = from_slice(&encoded)?;
assert_eq!(user, decoded);
Ok(())
}§Comprehensive Examples
This document provides a wide range of examples demonstrating how to use shmp for various serialization and deserialization tasks.
§1. Direct Serialization with serde
This is the most efficient and common way to use shmp.
§Basic Struct
use serde::{Serialize, Deserialize};
use shmp::{to_vec, from_slice, to_writer, from_reader};
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct User {
id: u32,
name: String,
}
let user = User { id: 1, name: "shmp".to_string() };
// Serialize to a byte vector and back.
let encoded = to_vec(&user).unwrap();
let decoded: User = from_slice(&encoded).unwrap();
assert_eq!(user, decoded);
// You can also serialize to any `io::Write` stream.
let mut buffer = Vec::new();
to_writer(&mut buffer, &user).unwrap();
// And deserialize from any `io::Read` stream.
let mut cursor = std::io::Cursor::new(&buffer);
let decoded_from_reader: User = from_reader(&mut cursor).unwrap();
assert_eq!(user, decoded_from_reader);§Enum Variants
shmp correctly serializes all styles of enum variants according to serde’s data model.
use serde::{Serialize, Deserialize};
use shmp::{to_vec, from_slice};
#[derive(Serialize, Deserialize, Debug, PartialEq)]
enum Message {
Quit, // Unit variant -> "Quit"
Write(String), // Newtype variant -> {"Write": "Hello"}
Move(i32, i32), // Tuple variant -> {"Move": [10, 20]}
Update { id: u64, value: String }, // Struct variant -> {"Update": {"id": 1, "value": "..."}}
}
let quit = Message::Quit;
let write = Message::Write("Hello".to_string());
let mv = Message::Move(10, 20);
let update = Message::Update { id: 1, value: "new".to_string() };
let encoded = to_vec(&quit).unwrap();
let decoded: Message = from_slice(&encoded).unwrap();
assert_eq!(decoded, quit);
let encoded = to_vec(&write).unwrap();
let decoded: Message = from_slice(&encoded).unwrap();
assert_eq!(decoded, write);
let encoded = to_vec(&mv).unwrap();
let decoded: Message = from_slice(&encoded).unwrap();
assert_eq!(decoded, mv);
let encoded = to_vec(&update).unwrap();
let decoded: Message = from_slice(&encoded).unwrap();
assert_eq!(decoded, update);§Zero-Copy Deserialization
For maximum efficiency, you can deserialize string slices (&str) and byte slices (&[u8]) without allocating new memory. This requires using the #[serde(borrow)] attribute.
Note that zero-copy deserialization is only available when using from_slice, as it requires the entire byte slice to be available in memory to borrow from.
use serde::{Serialize, Deserialize};
use shmp::{to_vec, from_slice};
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Packet<'a> {
#[serde(borrow)]
command: &'a str,
#[serde(borrow, with = "serde_bytes")] // This is essential for &[u8]
payload: &'a [u8],
}
let original = Packet {
command: "SET",
payload: &[0xDE, 0xAD, 0xBE, 0xEF],
};
let encoded = to_vec(&original).unwrap();
// The `decoded` struct borrows its data directly from the `encoded` slice.
let decoded: Packet = from_slice(&encoded).unwrap();
assert_eq!(original, decoded);§Custom Extension (Ext) Types
You can serialize your custom types into the MessagePack Ext format by implementing EncodeExt/DecodeExt and using #[serde(with = "shmp::ext_format")].
#[derive(Debug, PartialEq)]
struct MyCustomType {
id: u32,
value: String,
}
impl EncodeExt for MyCustomType {
const EXT_TAG: i8 = 42;
fn to_ext_data(&self) -> Result<Vec<u8>, Error> {
let mut bytes = self.id.to_be_bytes().to_vec();
bytes.extend_from_slice(self.value.as_bytes());
Ok(bytes)
}
}
impl DecodeExt for MyCustomType {
const EXT_TAG: i8 = 42;
fn from_ext_data(data: &[u8]) -> Result<Self, Error> {
// Note: In production code, you should implement robust error handling
// to prevent panics from slicing or `unwrap()`, such as checking `data` length.
// This example is simplified for clarity.
let id = u32::from_be_bytes(data[0..4].try_into().unwrap());
let value = String::from_utf8(data[4..].to_vec())?;
Ok(MyCustomType { id, value})
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct DataPacket {
sequence: u64,
#[serde(with = "shmp::ext_format")]
payload: MyCustomType,
}
let packet = DataPacket {
sequence: 1,
payload: MyCustomType { id: 101, value: "hello".to_string() },
};
let encoded = to_vec(&packet).unwrap();
let decoded: DataPacket = from_slice(&encoded).unwrap();
assert_eq!(packet, decoded);§Using Ext Directly (Low-level)
In addition to the trait-based approach using #[serde(with = "...")], you can use the Ext struct to manipulate extension types directly at a low level. This is useful when you want to send tagged binary data without needing to define a full custom type.
// Manually create an Ext value with a custom tag (e.g., 77) and payload.
let original_ext = Ext(77, vec![10, 20, 30, 40]);
// It serializes like any other Serde-compatible type, but is specifically
// encoded into the MessagePack `ext` format, not a `map` like a typical struct.
let encoded = to_vec(&original_ext).unwrap();
assert_eq!(encoded[0], 0xd6_u8); // Verify it's a fixext 4 type
let decoded_ext: Ext = from_slice(&encoded).unwrap();
assert_eq!(original_ext, decoded_ext);
// You can then access the tag and data directly.
assert_eq!(decoded_ext.0, 77);
assert_eq!(decoded_ext.1, vec![10, 20, 30, 40]);§2. Dynamic Pack Enum
The Pack enum is useful when the data structure is not known at compile time. The pack! macro provides a convenient JSON-like syntax.
§Creating Pack values with pack!
use shmp::{pack, Pack};
// simple examples
assert_eq!(pack!(null), Pack::Nil);
assert_eq!(pack!(true), Pack::Boolean(true));
assert_eq!(pack!(1004), Pack::Int(1004));
assert_eq!(pack!("Hello"), Pack::String("Hello".to_string()));
assert_eq!(pack!(1000 + 4), Pack::Int(1004));
assert_eq!(pack!(if true { 1000 } else { 4 }), Pack::Int(1000));
assert_eq!(pack!(({ let x = 1004; x })), Pack::Int(1004));
assert_eq!(pack!(block { let x = 1004; x }), Pack::Int(1004));
// compound type example
let data = pack!({
"user_id": 1024,
"username": "shmp_user",
"tags": ["rust", "msgpack", "serde"],
"config": {
"retries": 3,
"timeout": null
}
});
assert_eq!(data["username"], pack!("shmp_user"));
assert_eq!(data["tags"][0], pack!("rust"));
assert_eq!(data["config"]["timeout"], pack!(null));§Interchangeability: Struct <-> Pack <-> bytes
shmp supports seamless conversion between strongly-typed structs, the dynamic Pack enum, and raw bytes.
No matter which path you choose (e.g., Struct -> bytes or Struct -> Pack -> bytes), the resulting MessagePack output is identical.
This flexibility allows you to select the best approach for your needs. Use direct struct conversion for performance and type-safety when the schema is known, or leverage the Pack enum as an intermediate representation for dynamic data manipulation.
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
struct Config {
theme: String,
features: Vec<String>,
}
let config = Config {
theme: "dark".to_string(),
features: vec!["A".to_string(), "B".to_string()],
};
// --- Serialization Path ---
// You can serialize a struct to bytes directly, or via an intermediate `Pack` value.
// The resulting bytes will be identical.
// 1. Struct -> bytes
let bytes_direct = to_vec(&config).unwrap();
// 2. Struct -> Pack -> bytes
let pack_value = to_pack(&config).unwrap();
let bytes_via_pack = to_vec(&pack_value).unwrap();
assert_eq!(bytes_direct, bytes_via_pack);
// --- Deserialization Path ---
// Similarly, you can deserialize bytes directly to a struct, or via `Pack`.
// 1. bytes -> Struct
let decoded_direct: Config = from_slice(&bytes_direct).unwrap();
// 2. bytes -> Pack -> Struct
let pack_from_bytes: Pack = from_slice(&bytes_direct).unwrap();
let decoded_via_pack: Config = from_pack(pack_from_bytes).unwrap();
assert_eq!(config, decoded_direct);
assert_eq!(decoded_direct, decoded_via_pack);Modules§
- ext_
format - Provides a generic module for use with
#[serde(with = "...")]to serialize and deserialize custom types as the MessagePackExtformat.
Macros§
- pack
- Creates a
shmp::Packvalue using a JSON-like syntax.
Structs§
- Ext
- A low-level struct for directly handling MessagePack’s
extformat, representing a(type, data)pair. - MpSerializer
- A
Serializerfor the MessagePack format. - Pack
Deserializer - A
serde::Deserializerthat deserializes aPackenum value into a Rust value. - Pack
Serializer - A
serde::Serializerthat serializes Rust values into thePackenum. - Slice
Deserializer - A
Deserializerfor the MessagePack format from a byte slice. - Stream
Deserializer - A
Deserializerfor the MessagePack format.
Enums§
- Error
- The error type for
shmpoperations, covering I/O, serialization, and data conversion issues. - Pack
- A self-describing, dynamic MessagePack value.
Traits§
- Decode
Ext - A trait for types that can be decoded from the data part of a MessagePack
Extformat. - Encode
Ext - A trait for types that can be encoded into the data part of a MessagePack
Extformat.
Functions§
- from_
pack - Converts a
Packenum value into an instance of typeT. - from_
reader - A convenience function to deserialize a Rust value from a generic
io::Read. - from_
slice - A convenience function to deserialize a Rust value from a
&[u8]slice. - read_
pack - Reads a single MessagePack value from a reader and decodes it into a
Packenum. - to_pack
- Converts a value that implements
serde::Serializeinto aPackenum value. - to_vec
- A convenience function to serialize a Rust value into a
Vec<u8>. - to_
writer - A convenience function to serialize a Rust value to a generic
io::Write. - write_
pack - Encodes a
Packenum into its MessagePack binary representation and writes it to a writer.