# penum [Github](https://github.com/viktorlott/penum) [Download](https://github.com/viktorlott/penum) [crates.io](https://crates.io/crates/penum) `penum` is a procedural macro that is used to make an enum follow a given pattern, which can include generics with trait bounds. ## Installation This crate is available on [crates.io](https://crates.io/crates/penum) and can be used by adding the following to your project's Cargo.toml: ```toml [dependencies] penum = "0.1.6" ``` Or run this command in your cargo project: ```sh $ cargo add penum ``` ## Overview A `pattern` consists of one or more `shapes` and an optional `where clause`, which would auto bind all the concrete types that matches your pattern--with the trait bounds you've specified. - A `shape` can either be `Named`, `Unnamed` or `Unit`, and are used to validate variants. - A `where` clause is used to bind generic parameters to traits. - A `generic` parameter **CAN ONLY** be declared with capital letters or underscore. e.g `(T, FOO, BAR)` are valid generic parameters, but `(t, Foo, BaR)` are not, they are considered as **concrete** types. #### Future plans - `Static dispatch` - which would auto implement common `std` traits. - `Spread/range operator` - which would allow variadic fields `(T, U, ..) | {num: T, ..}` - `Discriminants` - which might give support for `#ident(T) = func(#ident)`, or something.. #### Usage Normally, using a generic in an enum means that it gets applied to the whole enum, and not per variant. For example, if I want to specify that all variants should be a `tuple(T)` where T must implement `Copy`, I'd have to specify a generic for all variants: ```rust enum Foo where T: Copy, U: Copy, F: Copy { Bar(T), Ber(U), Bur(F) // But if I now want to add `Bor(D)` to this // enum, I'd have to add it manually, and then // bind that generic to impl copy. // Also, there is nothing stopping me from // changing the variant shape to `Bor(D, i32)`. } ``` This seems kind of tedious, because all we want to do is to make the enum conform to a specific pattern, like this: ```rust // This forces all current and future variants to // contain one field which must implement `Copy`. #[penum( (T) where T: Copy )] enum Foo { Bar(i32), Ber(u32), Bur(f32) } ``` ..which would expand to the first example above. ## Examples It's also possible to make an enum conform to multiple shapes by seperating a `shape` with `|` symbol, for example: ```rust #[penum( (T) | (T, T) | { num: T } where T: Copy )] enum Foo { Bar(i32), Ber(u32, i32), Bur { num: f32 } } ``` Also, If an enum should break a `pattern`, like if a variant doesn't implement the correct `Trait`, an error would occur: ```rust #[penum( (T) | (T, T) | { num: T } where T: Copy )] enum Foo { Bar(String), ^^^^^^ // ERROR: `String` doesn't implement `Copy` Ber(u32, i32), Bur { num: f32 } } ``` ..or if a variant doesn't match the specified `shape`: ```rust #[penum( (T) | (T, T) | { num: T } where T: Copy )] enum Foo { Bar(u32), Ber(u32, i32, i32), ^^^^^^^^^^^^^ // Found: `Ber(u32, i32, i32)` // Expected: `(T) | (T, T) | { num: T }` Bur { num: f32 } } ``` Sometime we don't care about specifying a `where clause` and just want our enum to follow a specific `shape`. This is done by specifing `_`: ```rust #[penum( (_) | (_, _) | { num: _ } )] enum Foo { Bar(u32), Ber(u32, i32, i32), Bur { num: f32 } } ``` ## Demo ```rust use penum::shape; trait Trait {} impl Trait for f32 {} impl Trait for i32 {} trait Advanced {} impl Advanced for usize {} #[penum( (T, T, U) | (T, U) | { name: T } where T: Trait, U: Advanced )] enum Vector3 { Integer(i32, f32, usize), Float(f32, i32, usize), } #[penum( { name: _, age: usize } where usize: Advanced )] enum Strategy<'a> { V1 { name: String, age: usize }, V2 { name: usize, age: usize }, V3 { name: &'a str, age: usize }, } #[penum( { name: &'a str, age: usize } )] enum Concrete<'a> { Static { name: &'a str, age: usize }, } ``` ```rust #[penum( tuple(_) )] enum Must<'a> { Static { name: &'a str, age: usize } ^^^^^^^^^^^^^^^^^^^^^^^^^^^ // Found: `Static { name : & 'a str, age : usize }` // Expected: `tuple(_)` } // Note that this shape has a name (`tuple`). Right now // it doesn't do anything,but there is an idea of using // regexp to be able to validate on Variant names too. // Also, there is thoughts about using these Idents to // specify other rules, like if penum should auto implement // a static dispatch for a certain pattern. But this could // also be done by other rules. #[penum( tuple(T) where T: Trait )] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `the trait bound `usize: Trait` is not satisfied` enum Must { Static (usize) } ```