#proc-macro #macro #named #named-arguments #generator #default-value #impl-block

macro orderless

Orderless/named functions in Rust. add!(b = 2); // 4.

6 releases

0.1.5 May 21, 2023
0.1.4 May 21, 2023

#1804 in Rust patterns

MIT license

23KB
452 lines

orderless!

orderless generates macros for you that allow you to use orderless/named functions in Rust.

#[make_orderless(defs(a = 2))]
fn add(a: usize, b: usize) -> usize {
	a + b
}

// Compiles to add(2, 2) for no runtime performance hit!
add!(b = 2); // 4

Features

  • Attribute macro.
  • Procedural macro.
  • Paths to functions (functions from crates and impl).
  • Default argument values.
    • Identifiers.
    • Expressions.
    • const and static variables.
    • Optionally don't provide a default value.
  • Shortcut identical name and value to just the name. a = a to a.
  • Attribute macro impl_orderless for make_orderless in impl blocks.

Docs

Documentation is provided on docs.rs.

How does it work?

call_orderless!

call_orderless! is the proc macro that does all the heavy lifting. It takes a bunch of info such as the function's name, the order of the arguments, and the default values.

call_orderless! {
	func = two,
	order(a, b),
	defs(a = false, b = false),
	args(a = true, b = false),
}

As you can see, using it on its own is pretty pointless. But it's perfect for other macros to pass info they have to it.

create_orderless!

create_orderless! is another helper macro. It simplifies the process of writing call_orderless! by generating a macro_rules! macro which has most of the info built in.

create_orderless! {
	func = two,
	order(a, b),
	defs(a = false, b = false)
}

// Generates...
// Note `order(...)` disappears because it's integrated into `defs(...)` by `create_orderless!`.
macro_rules! two {
	( $($arg_name:ident $(= $arg_value:expr)?),*$(,)? ) => {
		::orderless::call_orderless!(
			func = two,
			defs(a = false, b = false),
			args($($arg_name $(= $arg_value)?),*),
		)
	};
	() => {
		::orderless::call_orderless!(
			func = two,
			defs(a = false, b = false),
			args(),
		)
	};
}

// Called like...
two!(b = true);

Now you have a function-like macro which can be used very simply.

make_orderless

make_orderless is an attribute macro which simplifies the process even more by grabbing info already available in the function's definition.

#[make_orderless(defs(a = false, b = false))]
fn two<T>(a: T, b: T) -> (T, T) {
	(a, b)
}

// Generates the same thing as `create_orderless!`...

impl_orderless

The main problem with make_orderless is that since it generates a macro_rules! right there, it can't be used inside of impl blocks.

struct Args {}

impl Args {
	#[make_orderless(defs(a = false, b = false))] // ERROR!!
	pub fn two(a: bool, b: bool) -> (bool, bool) {
		(a, b)
	}
}

Fortunately, the impl_orderless macro makes this possible.

struct Args {}

#[impl_orderless]
impl Args {
	#[make_orderless(defs(a = false, b = false))] // SUCCESS!!
	pub fn two(a: bool, b: bool) -> (bool, bool) {
		(a, b)
	}
}

It does this by removing all the make_orderless attributes and converting them into create_orderless! outside of the impl block.

With all this chaining it's macro-ception. A macro that converts a macro to another macro, which creates a macro, which calls a macro. But in the end this is all compile-time and doesn't impact runtime performance at all. two!() simply compiles to two(false, false)!

Dependencies

~1.5–2MB
~39K SLoC