Müsli Documentation

repository·main·Indexed 19 days ago

https://github.com/udoprog/musli

A high-performance, generic binary serialization framework for Rust. Müsli provides a flexible alternative to Serde with multiple formats (packed, storage, wire, descriptive, and json) offering varying levels of upgrade stability and performance. The ecosystem includes musli-web for WASM-compatible websocket protocols and web framework integrations (Axum, Yew, web-sys), as well as musli-zerocopy for safe zero-copy serialization and deserialization using aligned buffers and portable byte orders.

Tokens
73.3K
Snippets
238
Records
288
Agent score
65%

What's inside Müsli

  1. Overview of musli-web

    main

    musli-web is a utility crate designed for working with web-based APIs using the [Müsli] serialization format. It provides specialized integrations for common Rust web frameworks and libraries to facilitate efficient communication.

    Key Integrations

    • Axum Json: Enables using Müsli for serialization and deserialization within Axum applications.
    • Axum ws::Server: Provides tools to build the server-side implementation of the websocket protocol provided by this crate.
    • Yew: Enables communication with websocket clients using a well-defined API.
    • web-sys: Provides integration for web-sys 0.3.x via the web03 module.

    Core Features

    • WASM-compatible Websocket Protocol: A convenient protocol supporting both request-reply patterns and broadcasts.
    • Channel Support: Allows the server to identify the source of a message and enables clients to correlate messages using Handle::channel.
  2. Use musli-zerocopy-macros for zero-copy operations

    main

    The musli-zerocopy-macros crate provides the procedural macros required to implement zero-copy serialization and deserialization within the Müsli ecosystem.

    To use these macros, you should primarily interact with the musli-zerocopy crate, which utilizes these macros to provide a high-performance, zero-copy interface. For detailed API documentation and usage patterns, refer to the official musli documentation on docs.rs.

  3. Understand Müsli's serialization formats and upgrade stability

    main

    Müsli provides several formats, each offering different trade-offs regarding size, speed, and upgrade stability. Upgrade stability refers to how well a format handles changes to the data model (like adding or reordering fields).

    Format Capabilities

    Formatreordermissingunknownself
    musli::packed (with #[musli(packed)])
    musli::storage
    musli::wire
    musli::descriptive
    musli::json

    Capability Definitions

    • reorder: Determines if fields must occur in the exact order specified in the type. If reorder is supported, you can change field order without breaking compatibility (provided names/indices are stable).
    • missing: Determines if the format can handle missing fields (e.g., using Option<T>). This is essential for evolving on-disk schemas.
    • unknown: Determines if the format can skip over unknown fields. This is critical for network communication (upgrade stability).
    • self: Determines if the format is self-descriptive, allowing the structure to be reconstructed without the original model (e.g., via musli::value).
  4. Identify the correct module version for Axum, Yew, or web-sys

    main

    The musli-web modules are organized by the version of the target crate they support. When importing, ensure you select the module matching your project's dependency versions:

    • Use axum08 for Axum 0.8.x.
    • Use yew022 for Yew 0.22.x.
    • Use yew023 for Yew 0.23.x.
    • Use web03 for web-sys 0.3.x.
  5. How modes work in Müsli

    main

    Unlike serde, Müsli allows a single data model to be serialized in different ways using modes. A mode is a type parameter that enables different attributes to apply depending on the encoder's configuration.

    • Default Behavior: If no mode is specified, the implementation applies to all modes (M). If at least one mode is specified, it is implemented for all modes present in the model, including the default Binary and Text modes.
    • Flexibility: Modes can be applied to any Müsli attribute, allowing a single struct to represent entirely different formats (e.g., a JSON object vs. a packed array).
    use musli::{Decode, Encode};
    use musli::json::Encoding;
    
    enum Alt {}
    
    #[derive(Decode, Encode)]
    #[musli(Text, name_all = "name")]
    #[musli(mode = Alt, packed)]
    struct Word<'a> {
        text: &'a str,
        teineigo: bool,
    }
    
    const TEXT: Encoding = Encoding::new();
    const ALT: Encoding<Alt> = Encoding::new().with_mode();
    
    let word = Word { text: "あります", teineigo: true };
    
    // Serializes as JSON object: {"text":"あります","teineigo":true}
    let out = TEXT.to_string(&word)?;
    
    // Serializes as packed array: ["あります",true]
    let out = ALT.to_string(&word)?;
  6. Skip fields and specify default values

    main

    You can use #[musli(skip)] to prevent a field from being encoded or decoded. When decoding a skipped field, Müsli will use Default::default() to construct the value unless a custom default is provided.

    To provide a custom default, use #[musli(default = <path>)] where <path> is a function that returns the field's type. You can combine these with skip to ensure a field is always populated with a specific value during decoding even if it's missing from the input.

    use musli::{Encode, Decode};
    
    #[derive(Encode, Decode)]
    struct Person {
        name: String,
        #[musli(skip)]
        age: Option<u32>, // Uses Default::default()
        #[musli(skip, default = default_country)]
        country: Option<String>, // Uses default_country()
    }
    
    fn default_country() -> Option<String> {
        Some(String::from("Earth"))
    }
  7. Use untagged enums with `#[musli(untagged)]`

    main

    The #[musli(untagged)] attribute encodes enum variants without any identifying tag.

    Decoding Behavior:

    • Variants are attempted in the order they are declared.
    • The first variant that successfully decodes is selected.

    Requirements & Limitations:

    • The decoder must implement Decoder::try_clone (or Decoder::decode_buffer if try_clone is unavailable).
    • Warning: Standard decoders do not support Decoder::try_clone when decoding &mut &[u8] because exclusive references cannot be cloned. This may affect decoding performance or compatibility in certain buffer-based scenarios.
    use musli::{Encode, Decode};
    
    #[derive(Debug, PartialEq, Encode, Decode)]
    #[musli(untagged)]
    pub enum Untagged {
        Person {
            name: String,
            age: u32,
        },
        #[musli(transparent)]
        Numbers(Numbers),
    }
  8. Restrict attributes to Encode or Decode only

    main

    Use meta attributes to limit the scope of other attributes to either the encoding or decoding phase. This is useful when a specific configuration is valid for one direction but not the other (e.g., certain packed formats that are supported for encoding but not for decoding enums).

    • #[musli(encode_only)]: Attributes only apply when implementing Encode.
    • #[musli(decode_only)]: Attributes only apply when implementing Decode.
    use musli::mode::Binary;
    use musli::{Decode, Encode};
    
    enum Packed {}
    
    #[derive(Encode, Decode)]
    #[musli(mode = Packed, encode_only, untagged)]
    enum Name<'a> {
        Full(&'a str),
        Given(&'a str),
    }
  9. Use Müsli modes to vary encoding/decoding behavior

    main

    The Encode and Decode traits include a mode parameter M, allowing the same type to have different implementations based on the selected mode.

    By default, Müsli provides two special modes:

    • Binary (and other custom modes): Uses indexed fields (equivalent to #[musli(name(type = usize))]).
    • Text: Uses literal text fields by their name (equivalent to #[musli(name(type = str))]).

    You can scope attributes to specific modes using the #[musli(mode = ..)] meta attribute. When used, any sibling attributes are applied to that specific mode instead of the default one.

    use musli::{Encode, Decode};
    use musli::mode::Binary;
    use musli::json::Encoding;
    
    #[derive(Encode, Decode)]
    struct Person<'a> {
        // This field uses the 'Text' mode with a specific name
        #[musli(Text, name = "name")]
        not_name: &'a str,
        age: u32,
    }
    
    const TEXT: Encoding = Encoding::new();
    const BINARY: Encoding<Binary> = Encoding::new().with_mode();
    
    // In Text mode, 'not_name' is encoded as "name"
    let named = TEXT.to_vec(&Person { not_name: "Aristotle", age: 61 })?;
    // Result: b'{"name":"Aristotle","age":61}'
    
    // In Binary mode, fields use indices (0, 1, etc.)
    let indexed = BINARY.to_vec(&Person { not_name: "Plato", age: 84 })?;
    // Result: b'{"0":"Plato","1":84}'
  10. Customize serialization with Müsli attributes

    main

    Müsli provides a system of attributes to customize how Encode and Decode are implemented. Attributes are categorized by their scope:

    • Meta attributes: Apply to the attribute itself to filter scope (e.g., encode_only or specifying a mode). These can be used on containers, variants, and fields.
    • Container attributes: Apply to the entire struct or enum.
    • Variant attributes: Apply to individual variants within an enum.
    • Field attributes: Apply to individual fields within a struct or an enum variant.
  11. How to use Müsli macros

    main

    The musli-macros crate contains the underlying macro implementations for Müsli, but its API is unstable and is not intended for direct use by end-users.

    To use Müsli macros in your project, you should instead use the public APIs provided by the musli or musli_core crates. These crates re-export the necessary macros and provide a stable interface for your application.

    /* Do NOT use musli-macros directly. 
       Use musli or musli_core instead. */
    
    // Example of what you should do:
    use musli::Serialize;
    use musli_core::Deserialize;
  12. Specify serialization modes using Meta attributes

    main

    You can define different behaviors for different serialization formats (modes) by using meta attributes. Sibling attributes will only apply when the specified mode is active.

    Supported shorthand modes:

    • #[musli(Binary)]
    • #[musli(Text)]

    For custom modes, use: #[musli(mode = <path>)].

    This allows a single data structure to have multiple distinct Encode/Decode implementations (e.g., one for binary and one for text) defined in parallel.

    use musli::{Encode, Decode};
    
    #[derive(Encode, Decode)]
    #[musli(Text, name(type = usize))]
    struct Person<'a> {
        name: &'a str,
        age: u32,
    }