macro_rules! eager { ( $($all:tt)* ) => { ... }; }
Expand description
Emulates eager expansion of macros.
§Example
#[macro_use]
extern crate eager;
//Declare an eager macro
eager_macro_rules!{ $eager_1
macro_rules! plus_1{
()=>{+ 1};
}
}
fn main(){
// Use the macro inside an eager! call to expand it eagerly
assert_eq!(4, eager!{2 plus_1!() plus_1!()});
}
§Usage
eager!
can wrap any code, and if that code contains a macro call, that macro will be
expanded before its consumer. This means:
- If a macro call is given as an argument to another macro, the first macro will be expanded first.
- All macros will be fully expanded before
eager!
expands. Therefore, otherwise illegal intermediate expansion steps are possible.
eager!
does not work with any macro; only macros declared using eager_macro_rules!
may be
used. Such macros are said to be eager!
-enabled.
To enable the use of non-eager!
-enabled macros inside an eager!
call,
a lazy!
block can be inserted. Everything inside the lazy!
block will be lazily expanded,
while everything outside it will continue to be eagerly expanded. Since, lazy!
reverts
to the usual rules for macro expansion, an eager!
block can be inserted inside the lazy!
block, to re-enable eager expansion for some subset of it.
§Cons
-
Because of the way
eager!
is implemented - being a hack of recursive macros - the compiler’s default macro recursion limit is quickly exceeded. Therefore,#![recursion_limit="256"]
must be used in most situations - potentially with a higher limit - such that expansion can happen. -
Debugging an eagerly expanded macro is very difficult and requires intimate knowledge of the implementation of
eager!
. There is no way to mitigate this, except to try and recreate the bug without usingeager!
. Likewise, the error messages the compiler will emit are exponentially more cryptic than they already would have been. -
Only
eager!
-enabled macros can be eagerly expanded, so existing macros do not gain much. Thelazy!
block alleviates this a bit, by allowing the use of existing macros in it, while eager expansion can be done around them. Luckily,eager!
-enabling an existing macro should not be too much trouble usingeager_macro_rules!
.
§Macro expansions
Rust is lazy when it comes to macro expansion. When the compiler sees a macro call, it will
try to expand the macro without looking at its arguments or what the expansion becomes.
Using eager!
, previously illegal macro expansions can be made possible.
The following is a non-exhaustive list of illegal macro patterns that can be used with eager!
.
§The arguments to a macro usually cannot be the resulting expansion of another macro call:
Say you have a macro that adds two numbers:
macro_rules! add{
($e1:expr, $e2:expr)=> {$e1 + $e2}
}
And a macro that expands to two comma-separated numbers:
macro_rules! two_and_three{
()=>{2,3}
}
You cannot use the expansion of two_and_three!
as an argument to add!
:
let x = add!(two_and_three!()); // error
The compiler will complain about no rule in add!
accepting two_and_three
, since it does not
get expanded before the add!
, who requires two expressions and not just one.
With eager expansion, this can be made possible:
#[macro_use]
extern crate eager;
eager_macro_rules!{ $eager_1
macro_rules! add{
($e1:expr, $e2:expr)=> {$e1 + $e2}
}
macro_rules! two_and_three{
()=>{2,3}
}
}
fn main(){
let x = eager!{add!(two_and_three!())};
assert_eq!(5, x);
}
§Macros are illegal in some contexts (e.g. as an identifier)
Say you have a macro that expands to an identifier:
macro_rules! id{
()=> {SomeStruct}
}
And want to use it to declare a struct:
struct id!(){
v: u32
}
This will not compile since macros are illegal in identifier position. The compiler does not check whether the expansion of the macro will result in valid Rust code.
With eager expansion, id!
will expand before the eager!
block , making it possible to use it
in an identifier position:
#[macro_use]
extern crate eager;
eager_macro_rules!{ $eager_1
macro_rules! id{
()=> {SomeStruct}
}
}
eager!{
struct id!(){
v: u32
}
}
fn main(){
let some_struct = SomeStruct{v: 4};
assert_eq!(4, some_struct.v);
}
To circumvent any restriction on where macros can be used, we can therefore just wrap
the code surrounding the macro call with eager!
. The eager!
must still be in a valid position,
but in the worst case it can be put around the whole item
(struct, trait, implement, function, etc.).
§No intermediate expansion step can include invalid syntax
Say we want to create a macro that interprets natural language, converting it into an expression.
We start by declaring a macro that interprets operator words:
macro_rules! op{
( plus ) => { + };
( minus ) => { - };
}
We then declare a macro that interprets integer words:
macro_rules! integer{
( one ) => { 1 };
( two ) => { 2 };
}
Lastly, we declare the top-level macro that uses the previous two macros to expand into an expression:
macro_rules! calculate{
( $lhs:tt $op:tt $rhs:tt ) => {
integer!{$lhs} op!{$op} integer!{$rhs}
};
}
Using this macro will fail to compile:
let x = calculate!(one plus two); //Error
Looking at the first expansion step we can see that three macro calls in a sequence are not a valid expression:
let x = integer!(one) op!{plus} integer!(two); //Error
We can circumvent this restriction, by having calculate!
wrap its output in an eager!
:
#[macro_use]
extern crate eager;
eager_macro_rules!{ $eager_1
macro_rules! op{
( plus ) => { + };
( minus ) => { - };
}
macro_rules! integer{
( one ) => { 1 };
( two ) => { 2 };
}
macro_rules! calculate{
( $lhs:tt $op:tt $rhs:tt ) => {
eager!{integer!{$lhs} op!{$op} integer!{$rhs}}
};
}
}
fn main(){
let x = calculate!(one plus two);
assert_eq!(3, x);
}
In this case, calculate!
does not actually have to be eager!
-enabled, since it is not inserted
into an eager!
block. Though - as per the conventions - we do enable it such
that others may later use it inside an eager!
block.
§Conventions
Since we expect the use of this macro to be broadly applicable, we propose the following conventions for the Rust community to use, to ease interoperability.
§Documentation
To make it clearly visible that a given macro is eager!
-enabled, its short rustdoc description
must start with a pair of brackets, within which a link to the official eager!
macro documentation
must be provided. The link’s visible text must be ‘eager!’ and
the brackets must not be part of the link.
§Auxiliary variable
The auxiliary variable that must always be provided to eager_macro_rules!
must use the identifier eager_1
. This makes it easier for everyone to
get used to its presence and ignore it. By having it be the same in every project,
no one has to think about why a given project uses some specific identifier.
§Trivia
-
Ironically,
eager!
is not technicallyeager!
-enabled. Instead, it ignores itself if it is nested or a macro expands into aneager!
block. Likewise,eager_macro_rules!
is noteager!
-enabled, though this might be possible. -
lazy!
is treated byeager!
as a keyword and not a macro. -
eager_macro_rules!
’s auxiliary variable is affectionately calledSimon
. This nickname should probably not be used as the identifier in production code. Before reaching production, though… -
Simon once had a brother called
Garkel
. -
It requires continuous effort from Emoun to not forcibly rename
eager_macro_rules!
toeager_macros_rule
.