#struct #enums #map

macro variants-struct

A derive macro to convert enums into a struct where the variants are members

2 releases

0.1.1 Jun 13, 2021
0.1.0 Jun 11, 2021

#570 in Procedural macros

Download history 16/week @ 2023-12-06 28/week @ 2023-12-13 19/week @ 2023-12-20 2/week @ 2024-01-24 4/week @ 2024-01-31 13/week @ 2024-02-07 28/week @ 2024-02-14 14/week @ 2024-02-21 26/week @ 2024-02-28 32/week @ 2024-03-06 67/week @ 2024-03-13 42/week @ 2024-03-20

170 downloads per month

MIT/Apache

26KB
212 lines

variants-struct

A derive macro to convert enums into a struct where the variants are members. Effectively, its like using a HashMap<MyEnum, MyData>, but it generates a hard-coded struct instead of a HashMap to reduce overhead.

Basic Example

Applying the macro to a basic enum (i.e. one without tuple variants or struct variants) like this:

use variants_struct::VariantsStruct;

#[derive(VariantsStruct)]
enum Hello {
    World,
    There
}

would produce the following code:

struct HelloStruct<T> {
    pub world: T,
    pub there: T
}

impl<T> HelloStruct<T> {
    pub fn new(world: T, there: T) -> HelloStruct<T> {
        HelloStruct {
            world,
            there
        }
    }

    pub fn get_unchecked(&self, var: &Hello) -> &T {
        match var {
            &Hello::World => &self.world,
            &Hello::There => &self.there
        }
    }

    pub fn get_mut_unchecked(&mut self, var: &Hello) -> &mut T {
        match var {
            &Hello::World => &mut self.world,
            &Hello::There => &mut self.there
        }
    }

    pub fn get(&self, var: &Hello) -> Option<&T> {
        match var {
            &Hello::World => Some(&self.world),
            &Hello::There => Some(&self.there)
        }
    }

    pub fn get_mut(&mut self, var: &Hello) -> Option<&mut T> {
        match var {
            &Hello::World => Some(&mut self.world),
            &Hello::There => Some(&mut self.there)
        }
    }
}

The members can be accessed either directly (like hello.world) or by using the getter methods, like:

let mut hello = HelloStruct::new(2, 3);
*hello.get_mut_unchecked(&Hello::World) = 5;

assert_eq!(hello.world, 5);
assert_eq!(hello.world, *hello.get_unchecked(&Hello::World));

The getters can be particularly useful with the enum-iterator crate. For basic enums, the checked-getters will always return Some(...), so using get_unchecked is recommended, but this is not the case when the enum contains tuple variants.

Keep in mind that the enum variants are renamed from CamelCase to snake_case, to be consistent with Rust's naming conventions.

Visibility

The struct fields are always pub, and the struct shares the same visibility as the enum.

Customizing the struct

Renaming

By default, the struct's name is <OriginalEnumName>Struct. You can set it to something else with the struct_name attribute. For example, this:

#[derive(VariantsStruct)]
#[struct_name = "SomeOtherName"]
enum NotThisName {
    Variant
}

will produce a struct with name SomeOtherName.

You can also rename the individual fields manually with the field_name attribute. For example, this:

#[derive(VariantsStruct)]
enum ChangeMyVariantName {
    #[field_name = "this_name"] NotThisName
}

Will produce the following struct:

struct ChangeMyVariantName<T> {
    this_name: T
}

Derives

By default no derives are applied to the generated struct. You can add derive macro invocations with the struct_derive attribute. For example, this:

use serde::{Serialize, Deserialize};

#[derive(VariantsStruct)]
#[struct_derive(Debug, Default, Serialize, Deserialize)]
enum Hello {
    World,
    There
}

would produce the following code:

#[derive(Debug, Default, Serialize, Deserialize)]
struct HelloStruct<T> {
    pub world: T,
    pub there: T
}

// impl block omitted

Trait Bounds

By default the struct's type argument T has no trait bounds, but you can add them with the struct_bounds attribute. For example, this:

#[derive(VariantsStruct)]
#[struct_bounds(Clone)]
enum Hello {
    World,
    There
}

would produce the following code:

struct HelloStruct<T: Clone> {
    # go_away: T,
    // fields omitted
}

impl<T: Clone> HelloStruct<T> {
    // methods omitted
}

Combinations

Note that many derives don't require that the type argument T fulfills any trait bounds. For example, applying the Clone derive to the struct only makes the struct cloneable if T is cloneable, and still allows un-cloneable types to be used with the struct.

So if you want the struct to always be cloneable, you have to use both the derive and the trait bound:

#[derive(VariantsStruct)]
#[struct_derive(Clone)]
#[struct_bounds(Clone)]
enum Hello {
    // variants omitted
}

These two attributes, and the struct_name attribute, can be used in any order, or even multiple times (although that wouldn't be very readable).

Tuple and Struct Variants

Tuple variants are turned into a HashMap, where the data stored in the tuple is the key (so the data must implement Hash). Unfortunately, variants with more than one field in them are not supported.

Tuple variants are omitted from the struct's new function. For example, this:

#[derive(VariantsStruct)]
enum Hello {
    World,
    There(i32)
}

produces the following code:

struct HelloStruct<T> {
    pub world: T,
    pub there: std::collections::HashMap<i32, T>
}

impl<T> HelloStruct<T> {
    fn new(world: T) -> HelloStruct<T> {
        HelloStruct {
            world,
            there: std::collections::HashMap::new()
        }
    }

    pub fn get_unchecked(&self, var: &Hello) -> &T {
        match var {
            &Hello::World => &self.world,
            &Hello::There(key) => self.there.get(&key)
                .expect("tuple variant key not found in hashmap")
        }
    }

    pub fn get_mut_unchecked(&mut self, var: &Hello) -> &mut T {
        match var {
            &Hello::World => &mut self.world,
            &Hello::There(key) => self.there.get_mut(&key)
                .expect("tuple variant key not found in hashmap")
        }
    }

    pub fn get(&self, var: &Hello) -> Option<&T> {
        match var {
            &Hello::World => Some(&self.world),
            &Hello::There(key) => self.there.get(&key)
        }
    }

    pub fn get_mut(&mut self, var: &Hello) -> Option<&mut T> {
        match var {
            &Hello::World => Some(&mut self.world),
            &Hello::There(key) => self.there.get_mut(&key)
        }
    }
}

Notice that the new function now only takes the world argument, and the unchecked getter methods query the hashmap and unwrap the result.

The same can also be done in struct variants that have only one field.

License: MIT OR Apache-2.0

Dependencies

~1.5MB
~37K SLoC