Github
Github Download Tests crates.io docs.rs
`penum` is a procedural macro that is used for **enum conformity** and **static dispatch**. This is done by specifying a declarative pattern that expresses how we should interpret the enum. It's a tool for asserting how enums should **look** and **behave** through simple expressive rust grammar. - **Patterns** — can be thought of as a *toy shape sorter* that sorts through enum variants and makes sure they fit. So each variant has a certain shape that must satisfy the patterns we've specified. There are 3 shapes to choose from, *tuples* `()`, *structs* `{}` and *units*. - **Predicates** — are used in combination with *patterns* to assert what the matched variants field types should implement. They can be expressed like a regular where clause, e.g `where T: Trait`. The *generic parameters* needs to be introduced inside a pattern fragment. - **Smart 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 in front of the trait you wish to be dispatched. ## 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.30" ``` Or run this command in your cargo project: ```sh $ cargo add penum ``` ## Latest feature Expressions for enum discriminats are syntactically allowed, but is rejected at a semantic level. So this feature unlocks this semantic lock for the developer such that `discriminants` can be writting as expression blocks. The current supported attributed proc macros are `ToString`, `Display`, `Into` and `Deref`. This could be useful as an alternative to const declarations, and also, to avoid inner attributes. ```rust #[penum::to_string] enum EnumVariants { Variant0 = "Return on match", Variant1(i32) = "Return {f0} on match", Variant2(i32, u32) = stringify!(f0, f1).to_string(), Variant3 { name: String } = format!("My string {name}"), Variant4 { age: u32 } = age.to_string(), Variant5 = EnumVariants::Variant0.to_string(), Variant6 { list: Vec } = { let string = list .iter() .map(ToString::to_string) .collect::(); format!("List: ({string})") }, Variant7, Variant8, // Note that default will not appear in the Enum, i.e `EnumVariants::default` will not exist. // Also, we might change this in the future, e.g. using `fallback` instead? default = "Variant7 and Variant8 will return this default" } let enum_variants = Enum::Variant0; println!("{}", enum_variants.to_string()); ``` Add one of the following to your enum to enable enum descriminant expressions. - `penum::to_string` — Useful when you only want to implement `ToString`. - `penum::fmt` — Useful when you want to implement `ToString` and `Display`. - `penum::into(T)` — Useful when you want to convert your variant `Into`. - `penum::deref(T)` — Useful when you want to utilize Rust auto dereferencer. - `penum::static_str` — Will implement `Deref` and `AsRef`, including helper methods like: `.as_str()` and `.static_str()`. Make sure to also try out `penum::penum` if you like this `feature`. Note that not interoperable with `penum::penum`, and should be used separatly, because they are mutually exclusive. Note that `penum::penum` might be changed into `penum::expr`, `penum::declare` or `pemum::express`. ------------------------------------------------------------------ ## Overview A `Penum` expression can look like this: ```console Dispatch symbol. | #[penum( (T) where T: ^Trait )] ^^^ ^^^^^^^^^ | | | Predicate bound. | Pattern fragment. ``` A `Penum` expression without specifying a pattern: ```console #[penum( impl Trait for Type )] ^^^^^^^^^^^^^^^^^^^ ``` *Shorthand syntax for `_ where Type: ^Trait`* Important to include `^` for traits that you want to dispatch. ```rust #[penum( impl Type: ^Trait )] ``` Note that in a penum impl for expression, no `^` is needed. ```rust #[penum( impl Trait for Type )] ``` In Rust 1.68.0, `From` for `{f32,f64}` has stabilized. That means you can do this. ```rust #[penum( impl From for {f32,f64} )] ```
### Trivial example Use `Penum` to automatically `implement` a trait for the enum. ```rust #[penum(impl String: ^AsRef)] enum Store { V0(), V1(i32), V2(String, i32), V3(i32, usize, String), V4(i32, String, usize), V5 { age: usize, name: String }, V6, } ``` - Will turn into this: ```rust impl AsRef for Store { fn as_ref(&self) -> &str { match self { Store::V2(val, ..) => val.as_ref(), Store::V3(_, _, val) => val.as_ref(), Store::V4(_, val, ..) => val.as_ref(), Store::V5 { name, .. } => name.as_ref(), _ => "", } } } ``` There is also support for user defined traits, but make sure that they are tagged before the enum. ```rust #[penum] trait Trait { fn method(&self, text: &str) -> &Option<&str>; } ```
Supported std traits `Any`, `Borrow`, `BorrowMut`, `Eq`, `AsMut`, `AsRef`, `From`, `Into`, `TryFrom`, `TryInto`, `Default`, `Binary`, `Debug`, `Display`, `LowerExp`, `LowerHex`, `Octal`, `Pointer`, `UpperExp`, `UpperHex`, `Future`, `IntoFuture`, `FromIterator`, `FusedIterator`, `IntoIterator`, `Product`, `Sum`, `Sized`, `ToSocketAddrs`, `Add`, `AddAssign`, `BitAnd`, `BitAndAssign`, `BitOr`, `BitOrAssign`, `BitXor`, `BitXorAssign`, `Deref`, `DerefMut`, `Div`, `DivAssign`, `Drop`, `Index`, `IndexMut`, `Mul`, `MulAssign`, `MultiMethod`, `Neg`, `Not`, `Rem`, `RemAssign`, `Shl`, `ShlAssign`, `Shr`, `ShrAssign`, `Sub`, `SubAssign`, `Termination`, `SliceIndex`, `FromStr`, `ToString`
`Penum` is smart enough to infer certain return types for non-matching variants. e.g `Option`, `&Option`, `String`, `&str`. It can even handle `&String`, referenced non-const types. The goal is to support any type, which we could potentially do by checking for types implementing the `Default` trait. Note, when dispatching traits with associated types, it's important to declare them. e.g `Add`. ## Examples Used penum to force every variant to be a tuple with one field that must implement `Trait`. ```rust #[penum( (T, ..) where T: Trait )] enum Guard { Bar(String), ^^^^^^ // ERROR: `String` doesn't implement `Trait` Bor(Option), ^^^^^^^^^^^^^^ // ERROR: `Option` doesn't implement `Trait` Bur(Vec), ^^^^^^^^^^^ // ERROR: `Vec` doesn't implement `Trait` Byr(), ^^^^^ // ERROR: `Byr()` doesn't match pattern `(T)` Bxr { name: usize }, ^^^^^^^^^^^^^^^ // ERROR: `{ nname: usize }` doesn't match pattern `(T)` Brr, ^^^ // ERROR: `Brr` doesn't match pattern `(T)` Bir(i32, String), // Works! Beer(i32) // Works! } ``` If you don't care about the actual pattern matching, then you could use `_` to automatically infer every shape and field. Combine this with concrete dispatch types, and you got yourself a auto dispatcher.
Under development For non-std types we rely on the `Default` trait, which means, if we can prove that a type implements `Default` we can automatically add them as return types for non-matching variants,
```rust #[penum( _ where Ce: ^Special, Be: ^AsInner )] enum Foo { V1(Al), V2(i32, Be), V3(Ce), V4 { name: String, age: Be }, } // Will create these implementations impl Special for Foo { fn ret(&self) -> Option<&String> { match self { Foo::V3(val) => val.ret(), _ => None, } } } impl AsInner for Foo { fn as_inner(&self) -> &i32 { match self { Foo::V2(_, val) => val.as_inner(), Foo::V4 { age, .. } => age.as_inner(), _ => &0, } } } ``` - It's identical to this: ```rust #[penum(impl Ce: ^Special, Be: ^AsInner)] ``` #### More details - **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*. They 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, ..}`. ### Future ideas that might be useful #### NOT SUPPORTED YET - WIP The thing is, most of the time, you'd most likely want implement things the normal way, but when you have a very tiny implementation planned, this might be good enough. ```rust #[penum] enum Enum { Variant0(String) = implement! { ToString => "My incoming string: {f0}", Deref[Target = str] => &**f0, AsRef[str] => f0, }, default = { ToString => "My custom fallback string", _ => Default::default() } } ``` ```rust #[penum] enum Enum { Variant0(String) = implement! { ToString => "My incoming string: {f0}", }, Variant1(&'static str, i32) = implement! { ToString => "My incoming string: {f0}", }, default = { ToString => "My custom fallback string", _ => Default::default() } } ``` ```rust #[penum] enum Enum { Variant0(String) = implement! { ToString { format!("My incoming string: {f0}") }, Deref { &**f0 }, AsRef { f0 }, }, default = implement! { ToString { "My custom fallback string" }, _ { Default::default() } } } ``` ```rust #[penum] enum Enum { Variant0(String) = implement! { ToString => { format!("My incoming string: {f0}") }, Deref => { &**f0 }, AsRef => { f0 }, }, default = implement! { ToString { "My custom fallback string" }, _ { Default::default() } } } ``` ```rust #[penum] enum Enum { Variant0(String) = implement! { ToString => { format!("My incoming string: {f0}") }, Deref => { &**f0 }, AsRef => { f0 }, }, default = implement! { ToString => { "My custom fallback string" }, _ => { Default::default() } } } ```