penum
is a procedural macro that is used to make an enum conform to a
given pattern that can include generics with trait bounds, which then
allows for static dispatching
. It's a tool for asserting how enums
should look and behave through simple expressive rust grammar.
Patterns — can be though of a toy shape sorter, where the enum
variants are shape pieces that are trying to fit the given pattern
we've expressed. There are 3 shapes to choose from, tuples ()
,
structs {}
and units.
Trait bounds — are used in combination with generic parameters
to assert what the matched variants field types should implement, and
can be expressed like this where T: Trait<Type>
. The generic
parameters actually needs to be introduced inside a pattern fragment.
Static dispatch — lets us express how an enum should behave in
respect to its variants. The symbol that is used to express this is
^
and should be put infront of the trait you wish to be dispatched,
e.g. (T) where T: ^AsRef<str>
. This is currently limited to rust std
and core library traits, but there's plans to extend support for
custom trait definitions soon.
Impls — can be seen as a shorthand for a concrete type that
implements this trait, and are primarily used as a substitute for
regular generic trait bound expressions. The look something like
this, (impl Copy, impl Copy) | {name: impl Clone}
Placeholders — are single unbounded wildcards, or if you are
familiar with rust, it's the underscore _
identifier and usually
means that something is ignored, which means that they will satisfy
any type (_, _) | {num: _}
.
Variadic — are similar to placeholders, but instead of only being
able to substitute one type, variadics can be substituted by 0 or more
types. Like placeholders, they are a way to express that we don't care
about the rest of the parameters in a pattern. The look something like
this(T, U, ..) | {num: T, ..}
Allowing developers to assert how an enum should look and behave.
This crate is available on crates.io
and can be used by adding the following to your project's Cargo.toml:
toml
[dependencies]
penum = "0.1.14"
Or run this command in your cargo project:
sh
$ cargo add penum
A Penum
expression can look like this:
```text
^^^ ^^^^^^^^
| |
| Predicate bound.
|
Pattern fragment.
``` note that there can be multiple patterns fragments and predicate bounds.
Here's how it would look like if we wanted to dispatch a trait. ```text
|
Dispatch symbol
```
Here he have an enum with two unary tuple variants where the parameter
type Struct
implements the trait Trait
. The goal is to be able to
call the trait method
through Foo
. This can be accomplished
automatically marking the trait with a dispatch symbol ^
.
```rust
enum Foo { V1(Struct), V2(Struct), } ```
rust
impl Trait for Foo {
fn method(&self, text: &str) {
match self {
V1(val) => val.method(text),
V2(val) => val.method(text),
}
}
}
Boilerplate code for aboves examples
rust
struct Struct;
trait Trait {
fn method(&self, text: &str);
}
impl Trait for Struct {}
It's also possible to make an enum conform to multiple shapes by
seperating a shape
with |
symbol, for example:
```rust
enum Foo {
Bar(String),
^^^^^^
// ERROR: String
doesn't implement Copy
Bor(i32),
Ber(u32, i32),
Bur { num: f32 }
}
..or if a variant doesn't match the specified `shape`:
rust
enum Foo {
Bar(u32),
Bor(i32),
Ber(u32, i32, i32),
^^^^^^^^^^^^^
// Found: Ber(u32, i32, i32)
// Expected: (T) | (T, T) | { num: T }
Bwr(String),
^^^^^^
// ERROR: String
doesn't implement Copy
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
enum Foo { Bar(u32), Bor(i32, f32), Ber(u32, i32), Bur { num: f32 } } ```
Other times we only care about the first varaint field implementing a trait: ```rust
enum Foo { Bar(u32), Bor(i32, f32), Ber(u32, i32), Bur { num: f32 } } ```
..or you could just use impl
expressions instead.
```rust
enum Foo { Bar(u32), Bor(i32, f32), Ber(u32, i32), Bur { num: f32 } } ```
Static dispatch
- auto implement core
/std
/custom
traits (read
more).```rust use penum::shape;
trait Trait {} impl Trait for f32 {} impl Trait for i32 {}
trait Advanced {} impl Advanced for usize {}
// (T, FOO, BAR)
are valid generic parameters, but (t, Foo, BaR)
are not,
// they are considered as concrete types.
enum Vector3 { Integer(i32, f32, usize), Float(f32, i32, usize), }
enum Strategy<'a> { V1 { name: String, age: usize }, V2 { name: usize, age: usize }, V3 { name: &'a str, age: usize }, }
enum Concrete<'a> { Static { name: &'a str, age: usize }, } ```
```rust
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.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
the trait bound
usize: Traitis not satisfied
enum Must {
Static (usize)
}
```
| Traits | Supported |
| ---------- | ------------- |
|Any
| :heavycheckmark: supported |
|Borrow
| :heavycheckmark: supported |
|BorrowMut
| :heavycheckmark: supported |
|Eq
| :heavycheckmark: supported |
|AsMut
| :heavycheckmark: supported |
|AsRef
| :heavycheckmark: supported |
|From
| :heavycheckmark: supported |
|Into
| :heavycheckmark: supported |
|TryFrom
| :heavycheckmark: supported |
|TryInto
| :heavycheckmark: supported |
|Default
| :heavycheckmark: supported |
|Binary
| :heavycheckmark: supported |
|Debug
| :heavycheckmark: supported |
|Display
| :heavycheckmark: supported |
|LowerExp
| :heavycheckmark: supported |
|LowerHex
| :heavycheckmark: supported |
|Octal
| :heavycheckmark: supported |
|Pointer
| :heavycheckmark: supported |
|UpperExp
| :heavycheckmark: supported |
|UpperHex
| :heavycheckmark: supported |
|Future
| :heavycheckmark: supported |
|IntoFuture
| :heavycheckmark: supported |
|FromIterator
| :heavycheckmark: supported |
|FusedIterator
| :heavycheckmark: supported |
|IntoIterator
| :heavycheckmark: supported |
|Product
| :heavycheckmark: supported |
|Sum
| :heavycheckmark: supported |
|Copy
| :heavycheckmark: supported |
|Sized
| :heavycheckmark: supported |
|ToSocketAddrs
| :heavycheckmark: supported |
|Add
| :heavycheckmark: supported |
|AddAssign
| :heavycheckmark: supported |
|BitAnd
| :heavycheckmark: supported |
|BitAndAssign
| :heavycheckmark: supported |
|BitOr
| :heavycheckmark: supported |
|BitOrAssign
| :heavycheckmark: supported |
|BitXor
| :heavycheckmark: supported |
|BitXorAssign
| :heavycheckmark: supported |
|Deref
| :heavycheckmark: supported |
|DerefMut
| :heavycheckmark: supported |
|Div
| :heavycheckmark: supported |
|DivAssign
| :heavycheckmark: supported |
|Drop
| :heavycheckmark: supported |
|Fn
| :heavycheckmark: supported |
|FnMut
| :heavycheckmark: supported |
|FnOnce
| :heavycheckmark: supported |
|Index
| :heavycheckmark: supported |
|IndexMut
| :heavycheckmark: supported |
|Mul
| :heavycheckmark: supported |
|MulAssign
| :heavycheckmark: supported |
|MultiMethod
| :heavycheckmark: supported |
|Neg
| :heavycheckmark: supported |
|Not
| :heavycheckmark: supported |
|Rem
| :heavycheckmark: supported |
|RemAssign
| :heavycheckmark: supported |
|Shl
| :heavycheckmark: supported |
|ShlAssign
| :heavycheckmark: supported |
|Shr
| :heavycheckmark: supported |
|ShrAssign
| :heavycheckmark: supported |
|Sub
| :heavycheckmark: supported |
|SubAssign
| :heavycheckmark: supported |
|Termination
| :heavycheckmark: supported |
|SliceIndex
| :heavycheckmark: supported |
|FromStr
| :heavycheckmark: supported |
|ToString
| :heavycheckmark: supported |
RangeLit
- variadic fields by range (T, U, ..4) | {num: T, ..3}
-
VariadicLit
- variadic fields with bounds (T, U, ..Copy) | {num:
T, ..Copy}
Discriminants
- support for #ident(T) = func(#ident)
, or
something..