Crate magic_args

Crate magic_args 

Source
Expand description

“Magic” declarative-style function arguments.

fn f0() { /* ... */ }
fn f1(x: i32) { /* ... */ }
fn f2(x: i32, z: usize) { /* ... */ }
async fn f3(y: &'static str) { /* ... */ }
async fn f4(y: &'static str, x: i32, z: usize) { /* ... */ }

#[derive(MagicArgs)]
struct Args(i32, &'static str, usize);

let args = Args(-42, "🦀", 42);

let _ = apply(f0, &args);
let _ = apply(f1, &args);
let _ = apply(f2, &args);
let _ = apply(f3, &args);
let _ = apply(f4, &args);

The original idea for this crate comes from axum’s route handlers, which do this exact thing with their arguments.

§Quick start

  1. You define a “set of arguments”.
#[derive(MagicArgs)]
pub struct MyArgs(&'static str, usize /*, ... */);

let args = MyArgs("Hello, world!", 42);
  1. You use apply to call a function of your choosing.
fn my_func(msg: &'static str) { /* ... */ }

let result = apply(my_func, args);
  1. Profit? 💰

*this product contains limitations which are not featured above

§How it works

All of the “magic” of this crate is done via Callable and Args. Everything else is purely cosmetic and for convinience.

This documentation will use the term “argument set” to refer to any type $T$ which implements Args<U> for some $U$.

Callable is defined in terms of $A$ and $T$. $A$ is the argument set which contains all arguments available to the function. $T$ is an ordered tuple containing the arguments the function expects to be given. Callable is implemented automatically for all FnOnce(T0, T1, ...) -> O (up to 32 arguments). The job of the Callable trait is to pick out what arguments the function wants from a given $A$. For this to be possible, $A$ must be bounded such that $T \subseteq A$. In other words; to be able to even call the function with the arguments it expects, we must have the arguments in the first place. For example, let $T = (T_0, T_1, T_2)$. It must be that $T_0, T_1, T_2 \in A$ or, in code, A: Args<T0> + Args<T1> + Args<T2>. Picking the correct argument from the argument set $A$ for each argument of the function is done with Args::get and the compiler’s type inference.

§Limitations

This whole crates operates on the type-level of arguments. For this reason, it is impossible to have a function which accepts 2 different instances of the same type. To see why this is, consider the following function:

fn f(x: i32, _y: i32) -> i32 { x }

Given its signature $f: (i32, i32) \to i32$ and an arguments set $A = \{ 42, -42 \}$. It is impossible to know the correct order of passing these values to $f$ such that it returns 42, or -42 for that matter. This means all values in the argument set must be of a different type. In cases like above when a function has 2 arguments of the same type, the value of the matching type in the argument set is passed twice. This incurs a Clone::clone call.

Traits§

Args
A “set of arguments” that contains T.
Callable
A trait to describe any kind of type that can be called.
MagicArgs
A convinience trait to provide the args.apply(f) syntax.

Functions§

apply
Apply f on args.

Derive Macros§

MagicArgsderive
A derive macro to help you create argument sets.