Generate enum from a trait, with converters between them

Overview

trait-enumizer

img

Generate enum from a trait, with converters between them.

Features

  • Generation of enum based on specified trait (or inherent impl) where each variant corresponds to a method. Arguments correpond to fields of the variants; return values, correspond to a sender of some channel.
  • Generation call functions function with a appropriate match generated inside. This allows the enum to be "applied" to an object implementing the trait.
  • Generation of a proxy that allows obtaining a sequence of enum values using method calls from original trait (if possible) or API similar to original. Proxy also helps dealing with "channelizing" return values.
  • Handling return values.
  • Handling async (inherent impl only).

The library can be used as a synchronisation mechanism or as a building block to build actors or remote procedure calls.

Main piece of library is enumizer attribute macro. It should be attached to trait or inherent impl. It copies input trait or impl to proc macro output (sans pseudo-derive-helpers attributes), then also generates the following items:

  • Enum where each variant represent a method
  • Zero or more "call functions"
  • Zero or more "proxy structs"

Parameters

#[trait_enumizer::enumizer(...)] accepts following parameters:

  • name= - Set the enum name. Required.
  • pub, pub_crate - Mark all generated items as pub or pub(crate) respectively
  • returnval= - Enable more complex mode where return values are handled. Affects API of other generated items (call_fns and proxies) as well. Input macros is used as a makeshift GAT trait with specialisation. See a dedicated README section for more info.
  • enum_attr - Inject custom attribute (e.g. enum_attr[derive(serde_derive::Serialize)]) into enum declaration. Can be repeated. You need to use square brackets for this.
  • inherent_impl - Base enum on an inherent impl instead of a trait.
  • call_fn() - See below.
  • proxy() - See below.

Example of attributes syntax:

#[trait_enumizer::enumizer(pub_crate, name=NewEnumName, call_fn(name=call_me,ref_mut,allow_panic), proxy(name=NewEnumNameProxy, Fn,unwrapping_impl))]

Call functions (call_fns)

Call functions are generated when you use call_fn() parameter. They use the following subparameters:

  • name= - name of the inherent method. Required.
  • ref or ref_mut (alias mut_ref) or once (alias move) - Accept the object by reference, by mutable reference or by value. Required.
  • allow_panic - Allow generation of the function with panic!() calls inside.
  • async - Generate async fn. Use send_async pseudomethod from returnval macro-class instead of send.
  • extra_arg_type( ) - Add additional argument to the try_call function. That argument will appear on all macro_class_name!(send(...)) callbacks.

Those functions are used to "convert" enum value into a method call. Call functions are generated as inherent impl functions of the generated enum. First argument is self. Second argument is the value of (or reference to) something implementing the trait you specified (skipping the trait in inherent_impl mode). Third argument is required if you specify extra_arg_type(). It is passed to returnval's send (or send_async) pseudomethod for customized handling of return values.

Example:

#[enumizer(name=QqqEnum,pub,call_fn(name=the_call,ref_mut))]
trait Qqq {
    ...
}

generates

enum QqqEnum { ... }
impl QqqEnum {
    pub fn the_call
   (
   self, o: 
   &
   mut I);
}
  

Proxies

Proxies are generated when you use proxy() parameter. They use the following subparameters:

  • Fn, FnMut, FnOnce - Set type of closure that the proxy will carry. Required.
  • name= - name of the generated struct. Required.
  • resultified_trait= - Also generate "resultified" trait instead of implementing try_* functions inherently.
  • infallible_impl - Make the proxy also implement the original trait, provided your sink function does not err.
  • unwrapping_impl - Make the proxy also implement the original trait, using unwrap where needed.
  • unwrapping_and_panicking_impl - Force proxy to implement the original trait, using panic!() calls where complication would fail because of ownership requirements.
  • extra_field_type(...) - Add additional second field to proxy struct. That field will be used as additional argument to macro_class_name!(create(...)) and macro_class_name!(recv(...)) callbacks.
  • async - Expect user-specified closure to return Future instead of just Result and use .awaits inside where appropriate.

A proxy is a generic tuple struct with a public field. That field should implement Fn, FnMut or FnOnce. Second field (also public) is created if you specify extra_field_type(). There are two generic parameters: error type (you choose it) and closure type. Proxies allow "converting" method calls to enum values (which get delivered to your closure). By default all input methods are renamed, having "try_" prepended. Typically they return Result<(), YourErrorType>, but in returnval mode some of them may return Result , YourErrorType> . There is async mode, which upgrades your function to return Future and makes all the try_* methods async. You can ask Enumizer to also generate "resultified" trait which proxy then implements (unless async, of course). async also affects returnval macro usage.

You can also ask Enumizer to make proxy implement the original trait (also unless async). There are two strategies for it: infallible (if return values are not used and your Fn opts out of error handling by using std::convert::Infallible) and unwrapping.

You can make async proxy for non-async original methods and vice versa.

Example (simplified):

#[enumizer(name=QqqEnum,proxy(FnMut,name=QqqProxy))]
trait Qqq {
    fn foo(&self,x : i32);
}

generates

enum QqqEnum { Foo{x:i32} }
struct QqqProxy
   
    (pub F)
    where F: FnMut(QqqEnum) -> Result<(), E>;
impl
    
      QqqProxy where ... {
    fn try_foo(&mut self, x: i32) -> Result<(), E>{
        (self.0)(QqqEnum::Foo{x})
    }
}

    
   

Pseudo-derive-helpers

Pseudo-derive-helpers are attributes that are handled by this library. You are supposed to used them inside your input trait or impl before method signature or before argument inside signature. Other (unknown) attributes are passed though unmodified.

  • #[enumizer_enum_attr[...]] - Forward specified attribute to generated enum. Example: #[enumizer_enum_attr[serde(rename="qqq")]]. Can be attached to functions (which become enum variants) or to function arguments (which become enum variant fields).
  • #[enumizer_return_attr[...]] - in returnval mode, attach custom attribute to the ret field of the enum.
  • #[enumizer_to_owned] - For reference argument type, use owned value instead of trying to put reference to enum (which may not work, unless 'static).

Returnval pseudotrait

If you want Enumizer to handle return values, you need a channel of some sort. Enumizer is flexible in channel choice. There are built in "classes" for some popular channel types, you may also need to implement a channel class yourself.

You specify channel class as value for returnval parameter, e.g. returnval=trait_enumizer::flume_class. Early in Enumizer design channel classes were traits using GAT, but now they are special macro_rules-based macros.

Here is API of a channel class:

macro_rules! my_channelclass {
    (Sender<$T:ty>) => { /* type of the `ret` field in enum variant */ };
    (SendError) => { /* Error type when failed to send to a channel. Must not depend on T */ };
    (RecvError) => { /* Error type when failed to receive from a channel */ };
    (create::<$T:ty>()) => {
         /* Expression returning (tx, rx) channel pair. `tx` must be of type `Sender`. */
         /* Used by proxies */
    };
    (send::<$T:ty>($channel:expr, $msg:expr /*, $extraarg:expr */)) => { 
         /* Expression used to send to the channel (for `call_fn`). You may need to map error type here */
    };
    (recv::<$T:ty>($channel:expr /*, $extrafield:expr */)) => { 
        /* Expression use to recv value from the channel (for proxy) */
     };
    (send_async::<$T:ty>($channel:expr, $msg:expr)) => { 
        /* Expression to send to channel from async `call_fn`s. Should include `.await` and error mapping */
    };
    (recv_async::<$T:ty>($channel:expr)) => { 
        /* Expression to receive from cahnnel in async proxies. Should include `.await`. */
     };
}

You are recommended to base your implementation on one of the built-in channel class (e.g. flume_class) or to use RPC sample as a template for trickier channel class.

Although returnval mechanism use "channel" terminology, Senders are not required to be actual channels. They may be some internal IDs, with the real channel being supplied as an additional argument.

When Enumizer encountres a method with return value, corresponding enum variant gains additional field named ret (a hard coded identifier). Type of this field is controlled by the channel class and may depend on the type of the return value. All interactions with this additional field go though channel class's pseudomethods.

Tests (also serve as documentation)

To understand how the crate functions, you can view some test files. For most samples there is corresponding "manual" sample, showing expanded version (sometimes slightly simplified) of the same test.

Note that those links may be broken on Docs.rs, use README on Github instead.

See also

You might also like...
🦸‍♂️ Recast migrates your old extensions to AndroidX, making them compatible with the latest version of Kodular.
🦸‍♂️ Recast migrates your old extensions to AndroidX, making them compatible with the latest version of Kodular.

Recast Recast helps make your old extensions compatible with Kodular Creator version 1.5.0 or above. Prerequisites To use Recast, you need to have Jav

A tool to subscribe to Twitch channels and store them efficiently on disk

twitch-messages A tool to subscribe to Twitch channels and store them efficiently on disk Build the Tools You can start by building the binaries that

💫 Small microservice to handle state changes of Kubernetes pods and post them to Instatus or Statuspages

💫 Kanata Small microservice to handle state changes of Kubernetes pods and post to Instatus 🤔 Why? I don't really want to implement and repeat code

1️⃣ el lisp number uno - one lisp to rule them all 🏆

luno el lisp number uno luno is the one lisp to rule them all. Still experimental, do not use it in production yet. goals embeddable small size simple

Peekable iterator that allows to peek the next N elements without consuming them.

peekaboo docs - crates.io Peekable iterator that allows to peek the next N elements without consuming them. It's no_std compatible by default. It also

One copy of Electron to rule them all.

chroma One copy of Electron to rule them all. chroma keeps a central, up-to-date version of Electron, and makes all your installed Electron apps use i

A crate providing a tracing-subscriber layer for formatting events so Datadog can parse them

Datadog Formatting Layer A crate providing a tracing-subscriber layer for formatting events so Datadog can parse them. Features Provides a layer for t

A lean, minimal, and stable set of types for color interoperation between crates in Rust.

This library provides a lean, minimal, and stable set of types for color interoperation between crates in Rust. Its goal is to serve the same function that mint provides for (linear algebra) math types.

Get a diff between two OpenAPI descriptions.

Get the difference between two OpenAPI descriptions.

Owner
Vitaly Shukela
Vitaly Shukela
Create virtual serial ports, connect them to physical serial ports, and create routes between them all.

Virtual Serial Port Router (vsp-router) Create virtual serial ports, connect them to physical serial ports, and create routes between them all. vsp-ro

Rob Donnelly 3 Nov 24, 2022
Proc. macro to generate C-like `enum` tags.

Continuous Integration Documentation Crates.io #[derive(EnumTag)] This crate provides a proc. macro to derive the EnumTag trait for the given Rust enu

Robin Freyler 5 Mar 27, 2023
Use enum to filter something, support | and & operator.

Filter Use enum to filter something, support | and & operator. Just need to implement Filter Trait with filter-macros crate. How to work Example #[add

上铺小哥 9 Feb 8, 2022
The efficient and elegant crate to count variants of Rust's Enum.

variant-counter The efficient and elegant crate to count variants of Rust's Enum. Get started #[derive(VariantCount)] #[derive(VariantCount)] pub enum

Folyd 16 Sep 29, 2022
"Philips Ambilight for desktops". A tool to generate color palettes from your desktop wallpaper and send them to Home Assistant.

Desktop Dye DesktopDye is an open source project written in Rust that allows users to have their lights paired with Home Assistant adjust to the most

Jeroen Meijer (Jay) 7 Feb 22, 2023
A simple to use rust package to generate or parse Twitter snowflake IDs,generate time sortable 64 bits unique ids for distributed systems

A simple to use rust package to generate or parse Twitter snowflake IDs,generate time sortable 64 bits unique ids for distributed systems (inspired from twitter snowflake)

houseme 5 Oct 6, 2022
A stack-allocated box that stores trait objects.

This crate allows saving DST objects in the provided buffer. It allows users to create global dynamic objects on a no_std environment without a global allocator.

Aleksey Sidorov 19 Dec 13, 2022
A framework for iterating over collections of types implementing a trait without virtual dispatch

zero_v Zero_V is an experiment in defining behavior over collections of objects implementing some trait without dynamic polymorphism.

null 13 Jul 28, 2022
A stack for rust trait objects that minimizes allocations

dynstack A stack for trait objects that minimizes allocations COMPATIBILITY NOTE: dynstack relies on an underspecified fat pointer representation. Tho

Gui Andrade 114 Nov 28, 2022
A lending version of the `Stream` trait

lending-stream A lending version of Stream API Docs | Releases | Contributing Installation $ cargo add lending-stream Safety This crate uses #![deny(u

Yosh 5 Aug 14, 2023