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.


Design

Müsli is designed with similar principles as [serde]. Relying on Rust's powerful trait system to generate code which can largely be optimized away. The end result should be very similar to a handwritten encoder / decoder.

The central components of the framework are the [Encode] and [Decode] derives. They are thoroughly documented in the [derives] module.


Usage

Add the following to your Cargo.toml:

toml musli = "0.0.7" musli-wire = "0.0.7"


Formats

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

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.

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 roughly as compact and efficient as [bincode] while [musli-wire] is comparable to something like [protobuf]*.


Examples

The following is an example of full upgrade stability using [musli-wire]:

```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"), }); ```

The following is an example of partial upgrade stability using [musli-storage]:

```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, }); ```

License: MIT/Apache-2.0