musli

github crates.io docs.rs build status

Müsli

Müsli is a flexible and generic binary serialization framework.

Müsli currently depends on [GATs] and is nightly-only

We make the following assumptions:

I've chosen to internally use the term "encoding", "encode", and "decode" because it's common terminology when talking about binary formats. It's also distinct from [serde]'s use of "serialization" allowing for the ease of using both libraries side by side if desired.

Formats

Formats are currently distinguished by supporting various degrees of upgrade stability. A fully upgrade stable serialization format must tolerate that one model can add fields that an older version of the model should be capable of ignoring.

```rust use musli::{Encode, Decode};

[derive(Debug, PartialEq, Encode, Decode)]

struct Version1 { name: String, }

[derive(Debug, PartialEq, Encode, Decode)]

struct Version2 { name: String, #[musli(default)] age: Option, }

let version2 = musliwire::tovec(&Version2 { name: String::from("Aristotle"), age: Some(62), })?;

let version1: Version1 = musli_wire::decode(&version2[..])?;

assert_eq!(version1, Version1 { name: String::from("Aristotle"), }); ```

Partial upgrade stability can still be useful as is the case of the musli-storage format below, because reading from storage only requires decoding to be upgrade stable. So if correctly managed with #[musli(default)] this will never result in any readers seeing unknown fields.

```rust use musli::{Encode, Decode};

let version2 = muslistorage::tovec(&Version2 { name: String::from("Aristotle"), age: Some(62), })?;

assert!(muslistorage::decode::<_, Version1>(&version2[..]).iserr());

let version1 = muslistorage::tovec(&Version1 { name: String::from("Aristotle"), })?;

let version2: Version2 = musli_storage::decode(&version1[..])?;

assert_eq!(version2, Version2 { name: String::from("Aristotle"), age: None, }); ```

The available formats and their capabilities are:

| | reorder? | missing? | unknown? | |-|-----------------|-----------------|--------------------| | [musli-storage] #[musli(packed)] | ✗ | ✗ | ✗ | | [musli-storage] | ✔ | ✔ | ✗ | | [musli-wire] | ✔ | ✔ | ✔ |

recorder? determines whether fields must occur in exactly the order in which they are specified. So reordering fields in such a struct would cause an error. This is only suitable for byte-oriented IPC where data models are strictly synchronized.

missing? determines if the reader can handle missing fields, as exemplified above. This is suitable for on-disk storage.

unknown? determines if the format can skip over unknown fields. This is suitable for network communication.

For every feature you drop, the format becomes more compact and efficient. musli-storage #[musli(packed)] for example is as compact and efficient as [bincode] while [musli-wire] is comparable to something like [protobuf]*.

Usage

Add it to your Cargo.toml:

toml musli = "0.0.3" musli-wire = "0.0.3"

The Encode and Decode derives

See the [derives] module for documentation on how to use the [Encode] and [Decode] derives.

Examples

Basic example which uses the [default encoding format]:

```rust use musli::{Encode, Decode};

[derive(Debug, PartialEq, Encode, Decode)]

struct Struct<'a> { name: &'a str, age: u32, }

let mut out = Vec::new();

let expected = Struct { name: "Aristotle", age: 61, };

musliwire::encode(&mut out, &expected)?; let actual = musliwire::decode(&out[..])?;

assert_eq!(expected, actual); ```

License: MIT/Apache-2.0