Expand description
§Documentation for deriving Encode
and Decode
The Encode
and Decode
derives allows for automatically implementing
Encode
and Decode
.
They come with a number of options for customizing their implementation, detailed below. But first we need to talk about modes.
§Attributes
-
Meta attributes which apply to the attribute itself. It is used to filter what scope the current attribute applies to, such as only applying to an
Encode
derive using#[musli(encode_only, ..)]
or a specific mode such as#[musli(mode = Json, ..)]
. -
Container attributes are attributes which apply to the
struct
orenum
. -
Variant attributes are attributes which apply to each individual variant in an
enum
. -
Field attributes are attributes which apply to each individual field either in a
struct
or anenum
variant.
§Modes
If you’ve paid close attention to the Encode
and Decode
traits you
might’ve noticed that they have an extra parameter called M
which stands
for “mode”.
This allows a single type to have more than one implementation of encoding traits, allowing for a high level of flexibility in how a type should be encoded.
When it comes to deriving these traits you can scope attributes to apply to
either any mode, the default mode, or a completely custom mode. This is
done using the #[musli(mode = ..)]
meta attribute like this:
use musli::{Encode, Decode};
enum Json {}
#[derive(Encode, Decode)]
#[musli(name_all = "index")]
#[musli(mode = Json, name_all = "name")]
struct Person<'a> {
name: &'a str,
age: u32,
}
What this means is that if we want to serialize Person
using named fields,
we can simply turn on the Json
mode for our given serializer. If we want
to revert back to the default behavior and use indexed fields we can instead
use Binary
.
use musli_json::Encoding;
const JSON_ENCODING: Encoding<Json> = Encoding::new().with_mode();
const DEFAULT_ENCODING: Encoding = Encoding::new();
let named = JSON_ENCODING.to_vec(&Person { name: "Aristotle", age: 62 })?;
assert_eq!(named.as_slice(), b"{\"name\":\"Aristotle\",\"age\":62}");
let indexed = DEFAULT_ENCODING.to_vec(&Person { name: "Plato", age: 84 })?;
assert_eq!(indexed.as_slice(), b"{\"0\":\"Plato\",\"1\":84}");
So the #[musli(mode)]
atttribute is supported in any position. And any of
its sibling attributes will be added to the given alternative mode, rather
the default mode.
§Meta attributes
Certain attributes affect which other attributes apply to a given context. These are called meta attributes.
Meta attributes are applicable to any context, and can be used on containers, variants, and fields.
§#[musli(mode = <path>)]
The attributes only apply to the given mode
.
The Person
struct below uses string field names when the Json
mode is
enabled, otherwise it uses default numerical field names.
use musli::{Encode, Decode};
enum Json {}
#[derive(Encode, Decode)]
#[musli(mode = Json, name_all = "name")]
struct Person<'a> {
name: &'a str,
age: u32,
}
§#[musli(encode_only)]
The attributes only apply when implementing the Encode
trait.
An example where this is useful is if you want to apply #[musli(packed)]
in a different mode, but only for encoding, since decoding packed types is
not supported for enums.
use musli::mode::Binary;
use musli::{Decode, Encode};
enum Packed {}
#[derive(Encode, Decode)]
#[musli(mode = Packed, encode_only, packed)]
enum Name<'a> {
Full(&'a str),
Given(&'a str),
}
§#[musli(decode_only)]
The attributes only apply when implementing the Decode
trait.
use musli::{Decode, Encode};
#[derive(Encode, Decode)]
#[musli(name_all = "name")]
struct Name<'a> {
sur_name: &'a str,
#[musli(decode_only, name = "last")]
last_name: &'a str,
}
§Container attributes
Container attributes apply to the container, such as directly on the
struct
or enum
. Like the uses of #[musli(packed)]
and
#[musli(name_all = "name")]
here:
use musli::{Encode, Decode};
#[derive(Encode, Decode)]
#[musli(packed)]
struct Struct {
/* the body of the struct */
}
#[derive(Encode, Decode)]
#[musli(name_all = "name")]
enum Enum {
/* the body of the enum */
}
§#[musli(name_all = "..")]
Allos for renaming every field in the container. It can take any of the following values:
index
(default) - the index of the field will be used.name
- the literal name of the field will be used.PascalCase
- the field will be converted to pascal case.camelCase
- the field will be converted to camel case.snake_case
- the field will be converted to snake case.SCREAMING_SNAKE_CASE
- the field will be converted to screaming snake case.kebab-case
- the field will be converted to kebab case.SCREAMING-KEBAB-CASE
- the field will be converted to screaming kebab case.
use musli::{Encode, Decode};
#[derive(Encode, Decode)]
#[musli(name_all = "PascalCase")]
struct PascalCaseStruct {
field_name: u32,
}
#[derive(Encode, Decode)]
#[musli(name_all = "name")]
struct NamedStruct {
field1: u32,
field2: u32,
}
If applied to an enum, it will instead rename all variants:
use musli::{Encode, Decode};
#[derive(Encode, Decode)]
#[musli(name_all = "PascalCase")]
enum PascalCaseEnum {
VariantName {
field_name: u32,
}
}
#[derive(Encode, Decode)]
#[musli(name_all = "name")]
enum NamedEnum {
Variant1 {
field1: u32,
},
Variant2 {
field1: u32,
},
}
§#[musli(transparent)]
This can only be used on types which have a single field. It will cause that field to define how that variant is encoded or decoded transparently without being treated as a field.
use musli::{Encode, Decode};
use musli_wire::tag::{Tag, Kind};
#[derive(Encode)]
#[musli(transparent)]
struct Struct(u32);
let data = musli_wire::to_vec(&Struct(42))?;
assert_eq!(data.as_slice(), vec![Tag::new(Kind::Continuation, 42).byte()]);
§#[musli(packed)]
This attribute will disable all tagging and the structure will simply be encoded with one field following another in the order in which they are defined.
A caveat of packed structures is that they cannot be safely versioned and the two systems communicating through them need to be using strictly synchronized representations.
This attribute is useful for performing simple decoding over “raw” bytes when combined with an encoder which does minimal prefixing and packs fields.
use musli::{Encode, Decode};
#[derive(Encode)]
#[musli(packed)]
struct Struct {
field1: u32,
field2: u32,
field3: u32,
}
let data = musli_storage::to_vec(&Struct {
field1: 1,
field2: 2,
field3: 3,
})?;
assert_eq!(data.as_slice(), vec![1, 2, 3]);
§#[musli(name_type = ..)]
This indicates which type any contained #[musli(name = ..)]
attributes
should have. Tags can usually be inferred, but specifying this field ensures
that all tags have a single well-defined type.
The following values are treated specially:
str
applies#[musli(name_all = "name")]
by default.[u8]
applies#[musli(name_all = "name")]
by default.
use core::fmt;
use musli::{Encode, Decode};
#[derive(Debug, PartialEq, Eq, Encode, Decode)]
#[musli(transparent)]
struct CustomTag<'a>(&'a [u8]);
impl fmt::Display for CustomTag<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.0, f)
}
}
#[derive(Encode, Decode)]
#[musli(name_type = CustomTag)]
struct Struct {
#[musli(name = CustomTag(b"name in bytes"))]
name: String,
}
#[derive(Encode, Decode)]
#[musli(name_type = CustomTag)]
enum EnumWithCustomTag {
#[musli(name = CustomTag(b"variant one"))]
Variant1 {
/* .. */
},
}
§#[musli(name_method = ..)]
This allows for explicitly setting which method should be used to decode names. Available options are:
"value"
(default) - the name is decoded as a value."unsized"
- the name is decoded as an unsized value, this is the default if for example#[musli(name_type = str)]
is used."unsized_bytes"
- the name is decoded as a unsized bytes, this is the default if for example#[musli(name_type = [u8])]
is used.
This can be overrided for values which are unsized, but cannot be determined
through heuristics. Such a type must also implement Decode
(for
"value"
), DecodeUnsized
, or DecodeUnsizedBytes
as appropriate.
§#[musli(bound = {..})]
and #[musli(decode_bound = {..})]
These attributes can be used to apply bounds to an Encode
or Decode
implementation.
These are necessary to use when a generic container is used to ensure that the given parameter implements the necessary bounds.
#[musli(bound = {..})]
applies to all implementations while
#[musli(decode_bound = {..})]
only applies to the Decode
implementation.
The latter allows for using the decode lifetime parameter (which defaults to
'de
).
use musli::{Decode, Encode};
#[derive(Clone, Debug, PartialEq, Encode, Decode)]
#[musli(bound = {T: Encode<M>}, decode_bound = {T: Decode<'de, M>})]
pub struct GenericWithBound<T> {
value: T,
}
§Enum attributes
§#[musli(tag = ..)]
This attribute causes the enum to be internally tagged, with the given tag. See enum representations for details on this representation.
#[derive(Encode, Decode)]
#[musli(name_all = "name", tag = "type")]
enum Message {
Request { id: String, method: String, params: Params },
Response { id: String, result: Value },
}
§Variant attributes
Variant attributes are attributes which apply to each individual variant
in an enum
. Like the use of #[musli(name = ..)]
here:
use musli::{Encode, Decode};
#[derive(Encode, Decode)]
#[musli(name_all = "name")]
enum Enum {
Variant {
/* variant body */
},
#[musli(name = "Other")]
Something {
/* variant body */
},
}
§#[musli(name = ..)]
This allows for renaming a variant from its default value. It can take any value (including complex ones) that can be serialized with the current encoding, such as:
#[musli(name = 1)]
#[musli(name = "Hello World")]
#[musli(name = b"box\0")]
#[musli(name = SomeStruct { field: 42 })]
(ifSomeStruct
implementsEncode
andDecode
as appropriate).
If the type of the tag is ambiguous it can be explicitly specified through
the #[musli(name_type)]
attribute.
§#[musli(pattern = ..)]
A pattern to match for decoding a variant.
This allows for more flexibility when decoding variants.
use musli::{Encode, Decode};
#[derive(Encode, Decode)]
enum Enum {
Variant1,
Variant2,
#[musli(pattern = 2..=4)]
Deprecated,
}
§#[musli(name_all = "..")]
Allos for renaming every field in the variant. It can take any of the following values:
index
(default) - the index of the field will be used.name
- the literal name of the field will be used.PascalCase
- the field will be converted to pascal case.camelCase
- the field will be converted to camel case.snake_case
- the field will be converted to snake case.SCREAMING_SNAKE_CASE
- the field will be converted to screaming snake case.kebab-case
- the field will be converted to kebab case.SCREAMING-KEBAB-CASE
- the field will be converted to screaming kebab case.
use musli::{Encode, Decode};
#[derive(Encode, Decode)]
enum PascalCaseEnum {
#[musli(name_all = "PascalCase")]
Variant {
field_name: u32,
}
}
#[derive(Encode, Decode)]
enum NamedEnum {
#[musli(name_all = "name")]
Variant {
field1: u32,
},
Variant2 {
field1: u32,
},
}
§#[musli(name_type = ..)]
This indicates which type any contained #[musli(tag = ..)]
attributes
should have. Tags can usually be inferred, but specifying this field ensures
that all tags have a well-defined type.
The following values are treated specially:
str
applies#[musli(name_all = "name")]
by default.[u8]
applies#[musli(name_all = "name")]
by default.
use core::fmt;
use musli::{Encode, Decode};
#[derive(Debug, PartialEq, Eq, Encode, Decode)]
#[musli(transparent)]
struct CustomTag<'a>(&'a [u8]);
impl fmt::Display for CustomTag<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.0, f)
}
}
#[derive(Encode, Decode)]
#[musli(name_type = usize)]
enum Enum {
#[musli(name = 0usize, name_type = CustomTag)]
Variant {
#[musli(name = CustomTag(b"field1"))]
field1: u32,
#[musli(name = CustomTag(b"field2"))]
field2: u32,
},
#[musli(name = 1usize, name_all = "name")]
Variant2 {
#[musli(name = "field1")]
field1: u32,
#[musli(name = "field2")]
field2: u32,
},
}
§#[musli(name_method = ..)]
This allows for explicitly setting which method should be used to decode field names. Available options are:
"value"
(default) - the name is decoded as a value."unsized"
- the name is decoded as an unsized value, this is the default if for example#[musli(name_type = str)]
is used."unsized_bytes"
- the name is decoded as a unsized bytes, this is the default if for example#[musli(name_type = [u8])]
is used.
This can be overrided for values which are unsized, but cannot be determined
through heuristics. Such a type must also implement Decode
(for
"value"
), DecodeUnsized
, or DecodeUnsizedBytes
as appropriate.
§#[musli(transparent)]
This can only be used on variants which have a single field. It will cause that field to define how that variant is encoded or decoded transparently without being treated as a field.
§#[musli(default)]
This defines the variant that will be used in case no other variant matches. Only one such variant can be defined.
use musli::{Encode, Decode};
#[derive(Debug, PartialEq, Eq, Encode, Decode)]
#[musli(name_all = "kebab-case")]
enum Animal {
Cat,
Dog,
#[musli(default)]
Unknown,
}
§Field attributes
Field attributes are attributes which apply to each individual field
either in a struct
or an enum
variant. Like the uses of #[musli(all)]
here:
use musli::{Encode, Decode};
#[derive(Encode, Decode)]
#[musli(name_all = "name")]
struct Struct {
#[musli(name = "other")]
something: String,
#[musli(skip, default = default_field)]
skipped_field: u32,
}
fn default_field() -> u32 {
42
}
#[derive(Encode, Decode)]
#[musli(name_all = "name")]
enum Enum {
#[musli(name_all = "name")]
Variant {
#[musli(name = "other")]
something: String,
}
}
§#[musli(skip)]
This attribute means that the entire field is skipped. If a field is decoded
it uses Default::default
to construct the value. Other defaults can be
specified with [#[musli(default = <path>)]
][#muslidefault–path].
use musli::{Encode, Decode};
#[derive(Encode, Decode)]
struct Person {
name: String,
#[musli(skip)]
age: Option<u32>,
#[musli(skip, default = default_country)]
country: Option<String>,
}
fn default_country() -> Option<String> {
Some(String::from("Earth"))
}
§#[musli(default [= <path>])]
When a field is absent or disabled with #[musli(skip)]
, this attribute
specifies that a default value should be used instead.
If #[musli(default)]
is specified, the default value is constructed using
Default::default
.
If #[musli(default = <path>)]
is specified, the default value is
constructed by calling the function at <path>
.
use musli::{Encode, Decode};
#[derive(Encode, Decode)]
struct Person {
name: String,
#[musli(default)]
age: Option<u32>,
#[musli(default = default_height)]
height: Option<u32>,
#[musli(skip, default = default_meaning)]
meaning: u32,
}
fn default_height() -> Option<u32> {
Some(180)
}
fn default_meaning() -> u32 {
42
}
§#[musli(name = ..)]
This allows for renaming a field from its default value. It can take any value (including complex ones) that can be serialized with the current encoding, such as:
#[musli(name = 1)]
#[musli(name = "Hello World")]
#[musli(name = b"box\0")]
#[musli(name = SomeStruct { field: 42 })]
(ifSomeStruct
implementsEncode
andDecode
as appropriate).
If the type of the tag is ambiguous it can be explicitly specified through
the #[musli(name_type)]
variant or container attributes.
§#[musli(pattern = ..)]
A pattern to match for decoding the given field.
This allows for more flexibility when decoding fields.
use musli::{Encode, Decode};
#[derive(Encode, Decode)]
struct Struct {
field1: u32,
field2: u32,
#[musli(pattern = 2..=4)]
other: u32,
}
§#[musli(packed)]
This specifies that encoding and decoding should happen through the
EncodePacked
and DecodePacked
traits, instead of the default
Encode
and Decode
.
These traits contained implementations which are biased towards encoding the field as a compact, non-future compatible pack. In essense, the fields are encoded “one after another” without any metadata separating them. So for packed fields, the order, types and number of the fields are important.
use std::collections::VecDeque;
use musli::{Decode, Encode};
#[derive(Decode, Encode)]
struct Container {
#[musli(packed)]
tuple: (u32, u64),
#[musli(packed)]
array: [u32; 4],
}
§#[musli(bytes)]
This specifies that encoding and decoding should happen through the
EncodeBytes
and DecodeBytes
traits, instead of the default
Encode
and Decode
.
These traits contained implementations which are biased towards encoding the field as an array of bytes.
use std::collections::VecDeque;
use musli::{Decode, Encode};
#[derive(Decode, Encode)]
struct Container<'de> {
#[musli(bytes)]
vec: Vec<u8>,
#[musli(bytes)]
vec_deque: VecDeque<u8>,
#[musli(bytes)]
bytes: &'de [u8],
}
§#[musli(with = <path>)]
This specifies the path to a module to use instead of the fields default
Encode
or Decode
implementations.
It expects encode
and decode
and decodee function to be defined in the path being specified, like this:
use musli::{Decode, Encode};
#[derive(Decode, Encode)]
struct Container {
#[musli(with = self::module)]
field: Field,
}
struct Field {
/* internal */
}
mod module {
use musli::{Decoder, Encoder};
use super::Field;
pub fn encode<E>(field: &Field, cx: &E::Cx, encoder: E) -> Result<E::Ok, E::Error>
where
E: Encoder,
pub fn decode<'de, D>(cx: &D::Cx, decoder: D) -> Result<Field, D::Error>
where
D: Decoder<'de>,
}
This can also be generic such as:
use musli::{Decode, Encode};
#[derive(Decode, Encode)]
struct Container {
#[musli(with = self::module)]
field: Field<u32>,
}
struct Field<T> {
/* internal */
}
mod module {
use musli::{Decoder, Encoder};
use super::Field;
pub fn encode<E, T>(field: &Field<T>, cx: &E::Cx, encoder: E) -> Result<E::Ok, E::Error>
where
E: Encoder,
pub fn decode<'de, D, T>(cx: &D::Cx, decoder: D) -> Result<Field<T>, D::Error>
where
D: Decoder<'de>,
}
More complete example:
use std::collections::HashSet;
use musli::{Encode, Decode};
pub struct CustomUuid(u128);
#[derive(Encode, Decode)]
struct Struct {
#[musli(with = self::custom_uuid)]
id: CustomUuid,
#[musli(with = self::custom_set)]
numbers: HashSet<u32>,
}
mod custom_uuid {
use musli::{Context, Decode, Decoder, Encode, Encoder};
use super::CustomUuid;
pub fn encode<E>(uuid: &CustomUuid, cx: &E::Cx, encoder: E) -> Result<E::Ok, E::Error>
where
E: Encoder,
{
uuid.0.encode(cx, encoder)
}
pub fn decode<'de, D>(cx: &D::Cx, decoder: D) -> Result<CustomUuid, D::Error>
where
D: Decoder<'de>,
{
Ok(CustomUuid(decoder.decode()?))
}
}
mod custom_set {
use std::collections::HashSet;
use std::hash::Hash;
use musli::{Context, Decode, Decoder, Encode, Encoder};
pub fn encode<E, T>(set: &HashSet<T>, cx: &E::Cx, encoder: E) -> Result<E::Ok, E::Error>
where
E: Encoder,
T: Encode<E::Mode> + Eq + Hash,
{
encoder.encode(set)
}
pub fn decode<'de, D, T>(cx: &D::Cx, decoder: D) -> Result<HashSet<T>, D::Error>
where
D: Decoder<'de>,
T: Decode<'de, D::Mode> + Eq + Hash,
{
decoder.decode()
}
}
§#[musli(skip_encoding_if = <path>)]
This adds a condition to skip encoding a field entirely if the condition is
true. This is very commonly used to skip over encoding Option<T>
fields.
use musli::{Encode, Decode};
#[derive(Encode, Decode)]
struct Person {
name: String,
#[musli(skip_encoding_if = Option::is_none)]
age: Option<u32>,
}
§#[musli(trace)]
This causes the field to use the DecodeTrace
/ EncodeTrace
when
encoding the field. This is left optional for types where enabling tracing
for the field requires extra traits to be implemented, such as HashMap<K, V>
where we’d need K
to implement fmt::Display
.
Without using the trace
attribute below, the keys in the values
field
would not be instrumented, so with a decoding error you’d see this:
.values: not numeric (at bytes 15-16)
Instead of this (where #[musli(trace)]
is enabled):
.values[Hello]: not numeric (at bytes 15-16)
use std::collections::HashMap;
use musli::{Encode, Decode};
#[derive(Encode, Decode)]
struct Collection {
#[musli(trace)]
values: HashMap<String, u32>,
}
§Enum representations
Müsli supports the following enum representations, which mimics the ones supported by serde:
- Externally tagged (default).
- Internally tagged when
#[musli(tag = ..)]
is specified on the enum. - Adjacently tagged when both
#[musli(tag = ..)]
and#[musli(content)]
are specified.
§Externally tagged
#[derive(Encode, Decode)]
enum Message {
Request { id: String, method: String, params: Params },
Response { id: String, result: Value },
}
When an enum is externally tagged it is represented by a single field indicating the variant of the enum.
{"Request": {"id": "...", "method": "...", "params": {...}}}
This is the most portable representation and is supported by most formats.
It has special support in the Encoder
and Decoder
traits through
Encoder::encode_variant
and Decoder::decode_variant
.
Conceptually this can be considered as a “pair”, where the variant tag can be extracted from the format before the variant is decoded.
§Internally tagged
#[derive(Encode, Decode)]
#[musli(name_all = "name", tag = "type")]
enum Message {
Request { id: String, method: String, params: Params },
Response { id: String, result: Value },
}
In JSON, the Message::Request
would be represented as:
{"type": "Request", "id": "...", "method": "...", "params": {...}}
This is only supported by formats which are self descriptive, which is a
requirement for the format to be buffered through Decoder::decode_buffer
.
It is necessary to buffer the value, since we need to inspect the fields of
a map for the field corresponding to the tag
, and then use this to
determine which decoder implementation to call.