#deserialize #reflection #serialization #schema #validation

shapely

One trait for reflection, serialization, deserialization

5 releases (3 major breaking)

Uses new Rust 2024

new 3.0.0 Mar 11, 2025
2.0.1 Mar 11, 2025
1.0.0 Mar 10, 2025
0.1.0-alpha.1 Mar 10, 2025

#989 in Encoding

Download history

108 downloads per month
Used in 5 crates

MIT/Apache

82KB
1.5K SLoC

shapely

experimental free of syn crates.io documentation MIT/Apache-2.0 licensed

[!IMPORTANT]

There is no stable shapely API as of now (even though it's >1.0.0). The design is very much still being explored.

Expect multiple major versions in the near future — (note left 2025-03-11)

shapely provides runtime reflection for Rust.

Any type that implements Shapely trait returns a Shape, which describes:

  • The memory layout of the type
  • Its innards: struct fields, underlying type for newtypes, etc.
  • How to drop it in place

The Partial type is able to allocate (or work from a &mut MaybeUninit<T>) any Shapely type, and gradually initialize its fields — until the fully-built value is moved out of the partial.

It comes with a derive macro that uses unsynn for speed of compilation.

Supported Formats

shapely supports deserialization from multiple data formats through dedicated crates:

Implementing Your Own Deserializer

To implement a custom deserializer for a new format, you'll need to work with the following key components from shapely:

Key Types

  • Partial: The central type for building shapely values incrementally
  • Shape: Describes the memory layout and structure of a type
  • Innards: Represents the internal structure (Scalar, Struct, etc.)
  • Scalar: Represents primitive types like String, u64, etc.

Implementation Pattern

  1. Create a function that takes a &mut Partial and your format's input (string, bytes, etc.)
  2. Examine the shape of the partial using partial.shape()
  3. Handle different shapes based on shape.innards:
    • For Innards::Scalar, use partial.scalar_slot() to get and fill the slot
    • For Innards::Struct, iterate through fields, using partial.slot_by_name(field_name) to access each field
    • Create nested Partial instances for complex fields and fill them recursively

Example Implementation Skeleton

use shapely_core::{Partial, Innards, Scalar, FieldError, Shape};
use std::convert::From;

#[derive(Debug)]
pub enum MyFormatError {
    UnsupportedType,
    UnsupportedShape,
    FieldError(FieldError),
}

impl From<FieldError> for MyFormatError {
    fn from(err: FieldError) -> Self {
        MyFormatError::FieldError(err)
    }
}

pub fn from_my_format(partial: &mut Partial, input: &[u8]) -> Result<(), MyFormatError> {
    let shape_desc = partial.shape();
    let shape = shape_desc.get();
    
    match &shape.innards {
        Innards::Scalar(scalar) => {
            let slot = partial.scalar_slot().expect("Scalar slot");
            // Parse scalar value from input and fill slot
            match scalar {
                Scalar::String => slot.fill("/* parsed string */".to_string()),
                Scalar::U64 => slot.fill(0u64),
                // Handle other scalar types
                _ => return Err(MyFormatError::UnsupportedType),
            }
        },
        Innards::Struct { .. } => {
            // Parse struct fields from input
            for (field_name, field_value) in [("field1", "value1"), ("field2", "value2")].iter() {
                let slot = partial.slot_by_name(field_name)?;
                
                // Create a partial for the field and fill it recursively
                let mut partial_field = Partial::alloc(slot.shape());
                // Recursively deserialize field_value into partial_field
                // ...
                slot.fill_from_partial(partial_field);
            }
        },
        // Handle other shapes as needed
        _ => return Err(MyFormatError::UnsupportedShape),
    }
    
    Ok(())
}

For more detailed examples, examine the implementation of existing deserializers like shapely-json or shapely-msgpack.

License

Licensed under either of:

at your option.

Dependencies

~195KB