Coder Social home page Coder Social logo

sigp / superstruct Goto Github PK

View Code? Open in Web Editor NEW
65.0 6.0 3.0 22.45 MB

Rust library for versioned data types

Home Page: https://sigp.github.io/superstruct/

License: Apache License 2.0

Rust 99.78% Makefile 0.22%
rust proc-macro subtyping schema

superstruct's Introduction

SuperStruct

test status crates.io

SuperStruct is a library for working with a family of related struct variants, where each variant shares some common fields, and adds in unique fields of its own.

For more information please see the SuperStruct Guide.

Project Showcase

SuperStruct is used in the following projects:

License

Apache 2.0

superstruct's People

Contributors

michaelsproul avatar realbigsean avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

superstruct's Issues

Clippy on CI

There are a few low-priority Clippy failures currently. It would be good to run Clippy on CI so that they can be fixed and stay that way

Feature request: Variant propagation in nested superstructs

Let's say we have a nested struct, similar to nested example, but it has two inner variants that depend on the Variant. All using the same names for variants:

#[superstruct(variants(A, B), variant_attributes(derive(Debug, Clone)))]
#[derive(Debug, Clone)]
pub struct Outer {
    #[superstruct(only(A))]
    pub inner: InnerA,
    #[superstruct(only(B))]
    pub inner: InnerB,

    #[superstruct(only(A))]
    pub inner2: Inner2A,
    #[superstruct(only(B))]
    pub inner2: Inner2B,
}

This works but gets complicated quickly. I'd like to propose adding a same_variants attribute or something similar, which allows writing the above code as follows:

#[superstruct(variants(A, B), variant_attributes(derive(Debug, Clone)))]
#[derive(Debug, Clone)]
pub struct Outer {
    #[superstruct(same_variants]
    pub inner: Inner,

    #[superstruct(same_variants)]
    pub inner2: Inner2,
}

Combining this with only(...) would make writing such nested structs easier (and more readable), especially if there are a lot of different variants.

The alternative of using the Inner enum normally (i.e. the enum inside of Outer is sometimes not an option either, as the nested example already demonstrates.

Should reject unused top-level attributes with `no_enum`

Presently superstruct will accept code like:

#[superstruct(no_enum, variants(A, B))]
#[derive(Serialize, Deserialize)]
pub struct Thing {
   ...
}

The derive(Serialize, Deserialize) doesn't make sense here because there's no top-level type. The attribute is silently ignored. We should instead raise a compile-time error or warning so this useless code can be deleted.

Capturing closures in mapping macros

Currently superstruct uses a fn type for the closure passed to a mapping macro, meaning that variables can't be captured.

The reason we use fn is that it's a reliable type-hint that helps disambiguate things for the compiler. It would be interesting to investigate alternatives, like hinting only on the arguments/return value, or using something like a const or static assert.

Add moving getters to `RefMut`

Presently the mutable getters on RefMut require that the self parameter has the same lifetime as the inner reference. So for a type like this Message, we generate getters as shown below:

#[superstruct(variants(A, B))]
struct Message {
    pub x: String,
    #[superstruct(only(B))]
    pub y: String,
}
impl MessageRefMut<'a> {
    fn x_mut(&'a mut self) -> &'a mut String;
    fn y_mut(&'a mut self) -> Result<&'a mut String, ()>;
}

Note how the getters require &'a mut self, when self also contains a &'a mut Message{A,B}.

This lifetime restriction prevents converting a MessageRefMut into a reference that outlives the RefMut itself, e.g.

// Does not compile
fn get_x(message: &mut Message) -> &mut String {
    message.to_mut().x_mut()
}

This pattern may be useful sometimes, and is possible with an ordinary mutable reference, i.e. if we had just &'a mut Message{A,B} we could get a &'a mut to one of its fields.

To fix the issue I propose we generate additional moving getters on RefMut that take self by value:

impl MessageRefMut<'a> {
    fn into_x_mut(self) -> &'a mut String;
    fn into_y_mut(self) -> Result<&'a mut String, ()>;
}

This is a similar pattern to hash_map::OccupiedEntry::into_mut.

NB: this issue doesn't affect Ref because it uses immutable references: message.to_ref().x() is fine.

Stabilise enum ordering

The HashMap used for the variants attribute randomises the ordering of enum variants, which is quite unintuitive and doesn't interact nicely with e.g. serde's untagged enum deserialisation which relies on ordering: https://serde.rs/enum-representations.html#untagged

Using a wrapper around Vec we could impose the ordering provided in the top-level superstruct(variants(..)) attribute

Generate Into<*Ref> for references to variants

// FIXME(altair): generate these impls in superstruct
impl<'a, T: EthSpec> From<&'a BeaconStateBase<T>> for BeaconStateRef<'a, T> {
    fn from(inner: &BeaconStateBase<T>) -> Self {
        BeaconStateRef::Base(inner)
    }
}

impl<'a, T: EthSpec> From<&'a BeaconStateAltair<T>> for BeaconStateRef<'a, T> {
    fn from(inner: &BeaconStateAltair<T>) -> Self {
        BeaconStateRef::Altair(inner)
    }
}

Generate mapping macros to abstract over variants

I've just implemented this pattern by hand, which comes up a lot when using superstruct:

#[macro_export]
macro_rules! map_beacon_block_variants {
    ($block:expr, $e:expr) => {
        match $block {
            BeaconBlock::Base(inner) => {
                let f: fn(BeaconBlockBase<_, _>) -> _ = $e;
                BeaconBlock::Base(f(inner))
            }
            BeaconBlock::Altair(inner) => {
                let f: fn(BeaconBlockAltair<_, _>) -> _ = $e;
                BeaconBlock::Altair(f(inner))
            }
            BeaconBlock::Merge(inner) => {
                let f: fn(BeaconBlockMerge<_, _>) -> _ = $e;
                BeaconBlock::Merge(f(inner))
            }
        }
    };
}

impl<E: EthSpec> From<BeaconBlock<E, FullPayload<E>>> for BeaconBlock<E, BlindedPayload<E>> {
    fn from(block: BeaconBlock<E, FullPayload<E>>) -> Self {
        map_beacon_block_variants!(block, |inner| inner.into())
    }
}

I think we could generate the map_beacon_block_variants macro using superstruct, and maybe some other macros with a similar structure, like mapping into a common type rather than Self.

Reject multiple top-level attributes

This compiles while ignoring the second attribute:

#[superstruct(variants(A, B))]
#[superstruct(variants(C, D))]
struct MyThing;

We should reject it, but it's not clear how to do this. See #38 and the TODO added there.

Fails silently with multiple attributes on a single field

@ethDreamer reports that superstruct silently accepts this, without applying the 2nd partial_getter attribute:

#[superstruct(only(Deneb))]
#[superstruct(partial_getter(copy))]
pub should_override_builder: bool,

whereas this works fine:

#[superstruct(only(Deneb), partial_getter(copy))]
pub should_override_builder: bool,

We should update the attribute parser so that it either:

  1. Errors if more than one superstruct attribute appears on a field, or
  2. Handles the subsequent attributes by combining them with the first, and only errors if there's a direct conflict.

Solution for multiple mutable field accesses

Unlike regular fields, superstruct's mutable getters don't lend themselves to disjoint mutable borrows, i.e. borrowing field1 and field2 simultaneously, where at least one of the borrows is mutable.

This can be worked around on a case-by-case basis by writing helper functions like this, but I wonder if we should generate these automatically for given pairs/lists of fields:

/// Convenience accessor for validators and balances simultaneously.
pub fn validators_and_balances_mut(&mut self) -> (&mut [Validator], &mut [u64]) {
    match self {
        BeaconState::Base(state) => (&mut state.validators, &mut state.balances),
        BeaconState::Altair(state) => (&mut state.validators, &mut state.balances),
    }
}

Generate by-value getters

Occasionally it's useful to be able to convert a struct into one of its fields without cloning, and without destructuring.

We could generate methods like:

pub fn into_field(self) -> Field {
    self.field
}

Although I'm not sure if it's worth the complexity

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.