# penum
[](https://github.com/viktorlott/penum)
[](https://github.com/viktorlott/penum)
[](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)
}
```