Expand description
§opt_args
This crate lets you easily derive macros to call functions and instantiate structs
without having to specify all of their arguments.
Wrap your function or struct inside an opt_args
body
to generate a macro that can be called with named optional arguments.
use opt_args::opt_args;
opt_args! {
fn function(a: i32, b: &str = "default", c: (u8,)?) -> (i32, &str, (u8,)) {
(a, b, c)
}
}
opt_args! {
#[derive(Debug, PartialEq, Eq)]
struct Struct {
x: i32,
y: i32 = 1,
z: i32?,
other: Option<Box<Self>>?,
}
}
assert_eq!(
function!(1, b = "not the default"),
(1, "not the default", (0,))
);
assert_eq!(
Struct!(4, z = 5),
Struct {
x: 4,
y: 1,
z: 5,
other: None
}
);
§Using the macro
To use the macro, just wrap the target item (function or struct) inside the macro body.
Here, you can use a special syntax that lets you easily mark arguments as optional and,
additionally, indicate their default value. To mark an argument as optional, put a ?
after the type.
To indicate the default value of an optional argument, use the syntax = value
after the type.
Here is an example:
opt_args! {
fn f(a: u8, b: u8 = 5, c: u8?) -> u8 {
a + b + c
}
}
let result = f!(1);
assert_eq!(result, 1 + 5 + 0);
In the example above, b
and c
are marked as optional. For b
a default value is given (5
),
while no default value is given for c
. This means that the value assigned to c
is the default
value for the type u8
.
When listing optional arguments in the item, it’s important that all optionals come after the non-optionals.
opt_args! {
fn f(a: u8, b: u8 = 5, c: u8) -> u8 {
a + b + c
}
}
The example above is not valid since argument c
is not optional,
but it comes after b
which is optional. In this case the macro will result in a compile error.
§Calling the function
To call the function, simply use the name of the function as a macro and pass first the positional required arguments, then the named optional arguments, like in the following:
opt_args! {
fn f(a: u8, b: u8 = 5, c: u8?) -> u8 {
a + b + c
}
}
let result = f!(1, c = 3);
assert_eq!(result, 1 + 5 + 3);
§Options
§Order of optionals
By default, named arguments must be passed in the same order as they are declared in the item.
The following example fails because a = 1
is passed after c = 3
,
but in the original function a
comes before c
:
opt_args! {
fn f(a: u8, b: u8 = 5, c: u8?) -> u8 {
a + b + c
}
}
let result = f!(1, c = 3, b = 0);
assert_eq!(result, 1 + 0 + 3);
This behavior can be changed with the shuffle
attribute. This attribute allows to call the
function with arbitrary order of named arguments:
opt_args! {
#[opt_args(shuffle)]
fn f(a: u8, b: u8 = 5, c: u8?) -> u8 {
a + b + c
}
}
let result = f!(1, c = 3, b = 1);
assert_eq!(result, 1 + 1 + 3);
IMPORTANT: this doesn’t come without disadvantage:
to obtain this result, opt_args
creates a macro that matches any possible
permutation of the given optional arguments. When applying the shuffle
attribute,
the number of possible permutations scales in the order of n!
, where n
is the number of
optional arguments.
While macro expansion has no impact on runtime, it may impact compile time
with a great number of optionals.
§Export the macro
By default, the generated macro is annotated with #[macro_export]
to make it possible to
use it from outside. To change this behavior, use the non_export
attribute:
mod macros {
opt_args! {
#[opt_args(shuffle, non_export)]
pub fn f(a: u8, b: u8 = 5, c: u8?) -> u8 {
a + b + c
}
}
}
use macros::f;
f!(1);
mod macros {
opt_args! {
#[opt_args(shuffle)]
pub fn f(a: u8, b: u8 = 5, c: u8?) -> u8 {
a + b + c
}
}
}
use macros::f;
f!(1);
Of course for the macro to work outside the original module, it’s needed that the original item is available in the same scope where the macro is used:
mod macros {
opt_args! {
#[opt_args(shuffle)]
fn f(a: u8, b: u8 = 5, c: u8?) -> u8 {
a + b + c
}
}
}
use macros::f;
f!(1);
In the above example the function macro macros::f
is reachable, but the function macros::f
is not.
§Rename the macro
It’s also possible to give the generated macro a different name than the original item:
opt_args! {
#[opt_args(rename = f_macro)]
fn f(a: u8, b: u8 = 5, c: u8?) -> u8 {
a + b + c
}
}
let result = f_macro!(1);
assert_eq!(result, f(1, 5, 0));
§Recursion
It’s also possible to use the generated macro inside the original function:
opt_args! {
fn f(a: u8 = 10, b: u8 = 1, c: u8 = 2) -> u8 {
if a == 0 {
b + c
} else {
f!(a = a - 1, c = c + 1)
}
}
}
let result = f!();
assert_eq!(result, 13);
§Generics and lifetimes
The macro supports any kind of generic types, lifetimes and type inference:
opt_args! {
fn generics<'a, 'b, 'c, T: 'c>(
a: i32?,
b: &'a str = "default",
c: (u128, f32)?,
d: Option<[String; 4]>?,
e: &'b str?,
f: Vec<T>?,
) -> (i32, &'a str, (u128, f32), Option<[String; 4]>, &'b str, Vec<T>) {
(a, b, c, d, e, f)
}
}
assert_eq!(
generics!(b = "!default"),
(0, "!default", (0, 0.0), None, "", Vec::<u8>::new())
);
assert_eq!(
generics!(e = "e", f = vec![9]),
(0, "default", (0, 0.0), None, "e", vec![9])
);
§Types that don’t implement Default
It’s possible to use the macro to mark as optional even a type that doesn’t implement Default
.
struct X {
x: usize
}
opt_args! {
fn f(a: X = X { x: 0 }, b: X?) -> usize {
a.x + b.x
}
}
In this case it’s impossible to call the generated macro f!
without passing b
as a named argument,
because it would result in an attempt to use Default::default()
as value for X
.
f!();
This would result in a call to f(X { x: 0 }, Default::default())
which would trigger the compile error:
the trait `Default` is not implemented for `X`
.
This may be useful to force the caller to pass the argument b
as a named argument.
§Structs
The syntax and usage of the macro for structs is the same as it is for functions:
opt_args! {
#[opt_args(shuffle)]
#[derive(Debug, PartialEq, Eq)]
struct Opt<'a, 'b, T: 'b> {
a: i32,
b: &'a str = "b",
c: &'b str = "c",
d: T?,
}
}
let result: Opt<'_, '_, Vec<u8>> = Opt! {4, c = "b", b = "c"};
assert_eq!(
result,
Opt {
a: 4,
b: "c",
c: "b",
d: vec![],
}
);
Macros§
- Wrap the item (function or struct) inside the macro to declare optional arguments