Functions are the programmer's primary tool of abstraction, but there are cases in which they are insufficient, because the programmer wants to abstract over concepts not represented as values. Consider the following example:
# enum t { special_a(uint), special_b(uint) };
# let input_1, input_2 : t;
# fn f() -> uint {
match input_1 {
special_a(x) => { return x; }
_ => {}
}
// ...
match input_2 {
special_b(x) => { return x; }
_ => {}
}
# return 0u;
# }
This code could become tiresome if repeated many times. However, there is
no reasonable function that could be written to solve this problem. In such a
case, it's possible to define a macro to solve the problem. Macros are
lightweight custom syntax extensions, themselves defined using the
macro_rules!
syntax extension:
# enum t { special_a(uint), special_b(uint) };
# let input_1, input_2 : t;
# fn f() -> uint {
macro_rules! early_return(
($inp:expr $sp:ident) => ( //invoke it like `(input_5 special_e)`
match $inp {
$sp(x) => { return x; }
_ => {}
}
);
)
// ...
early_return!(input_1 special_a)
// ...
early_return!(input_2 special_b)
# return 0;
# }
On the left-hand-side of the =>
is the macro invocation syntax. It is
free-form, excepting the following rules:
- It must be surrounded in parentheses.
$
has special meaning.- The
()
s,[]
s, and{}
s it contains must balance. For example,([)
is forbidden.
To take as an argument a fragment of Rust code, write $
followed by a name
(for use on the right-hand side), followed by a :
, followed by the sort of
fragment to match (the most common ones are ident
, expr
, ty
, pat
, and
block
). Anything not preceeded by a $
is taken literally. The standard
rules of tokenization apply,
So ($x:ident => (($e:expr)))
, though excessively fancy, would create a macro
that could be invoked like my_macro!(i=>(( 2+2 )))
.
The right-hand side of the =>
follows the same rules as the left-hand side,
except that $
need only be followed by the name of the syntactic fragment
to transcribe.
Going back to the motivating example, suppose that we wanted each invocation
of early_return
to potentially accept multiple "special" identifiers. The
syntax $(...)*
accepts zero or more occurences of its contents, much like
the Kleene star operator in regular expressions. It also supports a separator
token (a comma-separated list could be written $(...),*
), and +
instead of
*
to mean "at least one".
# enum t { special_a(uint),special_b(uint),special_c(uint),special_d(uint)};
# let input_1, input_2 : t;
# fn f() -> uint {
macro_rules! early_return(
($inp:expr [ $($sp:ident)|+ ]) => (
match $inp {
$(
$sp(x) => { return x; }
)+
_ => {}
}
);
)
// ...
early_return!(input_1 [special_a|special_c|special_d])
// ...
early_return!(input_2 [special_b])
# return 0;
# }
As the above example demonstrates, $(...)*
is also valid on the right-hand
side of a macro definition. The behavior of Kleene star in transcription,
especially in cases where multiple stars are nested, and multiple different
names are involved, can seem somewhat magical and intuitive at first.
Macros, as currently implemented, are not for the faint of heart. Even ordinary syntax errors can be more difficult to debug when they occur inside a macro,