Macros

From Rust Community Wiki
Revision as of 13:36, 1 June 2020 by Koxiaet (talk | contribs) (Created page with "Macros are a method of using code to write code. They can be used to extend Rust with extra syntactical parts (such as the variable number of arguments in <code>println!</code...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Macros are a method of using code to write code. They can be used to extend Rust with extra syntactical parts (such as the variable number of arguments in println!), make writing lots of duplicate code easier (e.g. macros are used to generate the identical methods of the primitive integer types in std), or to create entirely different sub-languages within Rust (e.g. inline-python).

There are two types of macro in Rust: declarative macros and procedural macros, or proc-macros. Declarative macros are defined using macro_rules!, and provide a simple method of substituting a macro invocation for code. Procedural macros have to be defined in their own crate, and allow any arbitrary Rust code to run which will transform Rust code into different Rust code. Procedural macros are far more powerful, but are more difficult to write and slow down the compilation process (as they have to be both compiled and run during the compilation process).

Declarative Macros

Declarative macros are defined with macro_rules!. They use a match-like syntax to determine which "branch" to use. Here is a simple macro that takes no arguments and expands to the string literal "Hello World!":

macro_rules! hello_world {
    () => { "Hello World!" };
}

This macro has one arm which takes no arguments. Attempting to call the macro with an argument will result in an error as no arms match. Its body is delimited with braces, which aren't included in the expansion. The trailing semicolon is optional for the last match arm, but is required for all other match arms.

It can be invoked in three ways:

  • Using parenthesis: hello_world!()
  • Using brackets: hello_world![]
  • Using braces: hello_world! { }

All three are completely equivalent, and whichever one is used is up to the user. There are conventions, however; function-like macros such as println! are written with parenthesis (like a function call), array-like macros such as vec! are written with brackets (like an array literal) and macros that take in larger blocks of code are written with braces (like code blocks).

Any code written in the parameters of a macro will only be accepted if it matches exactly (excluding whitespace); for example (foo) => { ... } will match macro!(foo) and macro!( foo ) but not macro!(foo,) or macro!(f oo). To accept variable parameters to the macro use a dollar sign, followed by the variable's name, followed by a colon, followed by the variable's type; for example, ($value:expr) will accept any expression and bind it to the variable $value. Then, in the body of the macro $value will be expanded to the expression given to the macro. The following types are available:[1]

Name Description Examples
ident Any valid Rust identifier including keywords foo, fn
block A brace-delimited block of Rust code { 4 + 5 }, { x = 10; }
stmt A statement, often delimited with a semicolon let x = 5;, if y { x = 10; }
expr An expression 2 + 2, -8.abs()
pat A pattern (value, _), Range { start, .. }
ty A type Vec<String>
lifetime A lifetime 'a, 'static
literal A literal "Hello World!", 5.6f32
path A path (a sequence of any number of identifiers separated by ::) ::std::fmt::Display, foo
meta A meta item (what goes inside the #[] of an attribute, excluding the hash and brackets) derive(Debug), warn(clippy::pedantic)
tt A single token tree, which can be an identifier, literal, lifetime, piece of punctuation, bracket-delimited sequence of token trees, using either parenthesis, brackets, braces 'static, (some more tokens)
item An item; this includes structs, enums, function declarations, modules, et cetera struct S;
vis A visibility indicator pub, pub(crate), pub(self), pub(in ...)

Repeats

Declarative macros can specify parts to be repeated multiple times, by writing $(part)suffix where suffix is one of ?, * or +. ? specifies that the area should be repeated zero or one times, * specifies zero or more and + specifies one or more. Any token can be placed before the suffix to cause that token to have to appear in between each repeat. Using these features, a common pattern to allow trailing commas is $(list_item),* $(,)?.

In the body of the macro the same rules apply to expand the repeated variables. The source code of the vec! macro can be approximated using this:

macro_rules! vec {
    ($($elem:expr),* $(,)?) => {
        {
            let mut vec = Vec::new();
            $(
                vec.push($elem);
            )*
            vec
        }
    }
}

Visibility of Declarative Macros

Token Munching

Declarative Macros 2.0

Procedural macros

Derive Macros

Attribute Macros

Procedural Macros

Testing Procedural Macros

Macro Hygiene