Crate orderless

source ·
Expand description

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)!

Macros

  • An internal procedural macro that calls a function based on a definition.
  • A procedural macro that’s meant for creating macros that run call_orderless! with the provided definition.

Attribute Macros

  • A procedural macro that allows make_orderless to be used in impl blocks.
  • A procedural macro that’s meant for creating macros that run call_orderless! with the provided definition.