Defunctionalize
Defunctionalization as a proc macro for Rust.
Note: this is in very early stages, and I am just starting to work out the mechanics.
Defunctionalization is a technique by which higher-order functions are eliminated, and replaced instead by a first-order function.
Though this reduces flexibility somewhat, there are certain benefits. In particular, it allows serialization of behaviour.
Features
- Defunctionalize all public functions in a module into an enum.
- Extra parameters in the method get moved to the enum.
- Can apply
derive
to the module to derive for the resulting enum.
Usage
-
Apply the
defunctionalize
attribute to a module. This attribute takes a function signature, which is the signature of the functions you intend to defunctionalize. This signature requires named arguments, and supports generics.Typically, the name of the generated enum type is computed from the name of the module, but by adding a name in this signature, that name is used instead. Note that the name is not converted to CamelCase automatically in this case.
// Basic usage // Generics are supported // The function name will override the module name // The generated item will be `enum DefuncC { ... }` // Where clauses are supported too
-
You may apply the
derive
attribute to this module as well. The syntax is the same as usual, and the traits will be derived on the generated enum. The usual restrictions will apply for the types of the enum cases' fields. -
Define
pub
functions in this module. They will get converted to enum cases. Non-pub
functions may be defined as helpers, but will not be added as enum cases.These functions must have at least the signature defined in the
defunctionalize
attribute, but may also have extra arguments before the listed ones. The return type must match.The name of the function is converted to CamelCase to become the name of the enum case.
Examples
The most basic usage is as follows:
use defunctionalize;
// Apply the defunctionalize attribute to a module. This attribute takes one parameter
// which is the signature of the generated `call` method.
//
// In this example, we are doing a mathematical operation, so our defunctionalized
// function takes two integers, and returns the result of the operation.
// The above module will be compiled into an enum, with a case for each
// public function:
//
// pub enum Operation {
// Add,
// Sub,
// Mult,
// Div,
// Rem,
// }
//
// The name of the module is converted to CamelCase to become the name of the enum.
// Similarly, the name of the functions are converted to CamelCase to become the case
// names
// Values of the resulting enum can then be passed to functions. They contain a `call`
// method, which uses the signature provided in the defunctionalize attribute, which
// calls the corresponding method
// And here we use the defunctionalized module to compute 6 * 7 = 42!
This, of course, is not the entire functionality of this technique! Here, we're going to expand on the above to demonstrate two more useful features:
use defunctionalize;
// First, notice that the function signature here has had one argument removed!
// Next, we put some derives *on the module*. These will get moved to the enum. This
// is most useful for serializing your defunctionalized functions.
// The extra parameter got moved to the enum! Now we can essentially serialize an
// entire function call.
//
// #[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
// pub enum Operation {
// Add(u32),
// Sub(u32),
// Mult(u32),
// Div(u32),
// Rem(u32),
// }
// Now we only need to provide one parameter to the `call` method:
// And here we use the defunctionalized module to compute 49 - 7 = 42!