1 unstable release
0.1.6 | Mar 4, 2023 |
---|
#1790 in Procedural macros
18KB
321 lines
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 and can be used by adding the following to your project's Cargo.toml:
[dependencies]
penum = "0.1.6"
Or run this command in your cargo project:
$ 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 beNamed
,Unnamed
orUnit
, 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 commonstd
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:
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:
// 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:
#[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:
#[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
:
#[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 _
:
#[penum( (_) | (_, _) | { num: _ } )]
enum Foo {
Bar(u32),
Ber(u32, i32, i32),
Bur { num: f32 }
}
Demo
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 },
}
#[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)
}
Dependencies
~1.5MB
~36K SLoC