#combinator #structs #simpler #metadata

destruct

Destruct structs and enums for simpler combinator implementation

3 releases

0.1.2 Nov 7, 2019
0.1.1 Oct 15, 2019
0.1.0 Oct 15, 2019

#1481 in Rust patterns

MIT license

16KB
272 lines

Destruct

Destruct structs and enums into a heterogeneous list consists of a fixed set of types, to enable a quick implementation of combinator libraries.

API

trait Destruct

type DestructType

The destructed object type

If your struct is:

#[derive(Destruct)]
struct YourStruct {
    field: YourField,
    field2: YourField2,
}

Then the DestructType is:

DestructBegin<Fields, m>
    where Fields = DestructField<YourField, NextField, m1>
          NextField = DestructField<YourField2, End, m2>
          End = DestructEnd<m>
    where m is some generated type implementing `trait DestructMetadata`
          m1 is the metadata for `field`, implementing `trait DestructFieldMetadata + DestructMetadata`
          m2 is the metadata for `field2`, implementing `trait DestructFieldMetadata + DestructMetadata`
}

Here is a list of types may appear in DestructType:

  • DestructBegin
  • DestructField
  • DestructEnd
  • DestructEnumBegin
  • DestructEnumVariant
  • DestructEnumEnd

fn destruct(self) -> Self::DestructType

Destruct self to destruct type

fn construct(d: Self::DestructType) -> Self;

Construct self from destruct type

Metadata

pub trait DestructMetadata {
    fn struct_name() -> &'static str;
    fn named_fields() -> bool;
}

pub trait DestructFieldMetadata: DestructMetadata + 'static {
    fn field_name() -> &'static str;
    fn field_index() -> usize;
}

pub trait DestructEnumMetadata {
    fn enum_name() -> &'static str;
}

pub trait DestructEnumVariantMetadata: DestructEnumMetadata + 'static {
    fn variant_name() -> &'static str;
    fn variant_index() -> usize;
}

Example

For example, here is how to implement a parser with destruct (see destruct-parser):

  1. Write a Parsable trait;
  2. Implement parsers for basic types;
  3. Implement parsers for six Destruct types;
  4. Derive Destruct for your struct by adding #[derive(Destruct)]
  5. (Optional) Implement a macro for deriving impl<T: Destruct> Parsable, namely parsable!;
  6. (Optional) Implement Parsable for your struct by adding #[destruct(parsable)]

Why do I need parsable! macro?

Because Rust forbids overlapping implementation of traits. Ideally what I need is the following trait implementation:

impl<T: Destruct> Parsable for T where T::DestructType: Parsable {}

But Rust complains:

upstream crates may add new impl of trait `destruct::Destruct` for type `destruct::DestructEnumBegin<_, _>` in future versions

So I added #[destruct(parsable)] to generate impls for every struct. It is equivalent to parsable!(YourStruct).

#[macro_export]
macro_rules! parsable {
    ($t:ident) => {
        impl Parsable for $t {
            fn parse<R: io::Read + Clone>(read: &mut R) -> Result<Self, Error> {
                <$t as Destruct>::DestructType::parse(read).map(<$t as Destruct>::construct)
            }
        }
    };
}

Limitations

Generics are not supported.

Dependencies

~1.5MB
~36K SLoC