Macros in Rust
Macros in Rust are a way of writing code that writes other code, which is known as metaprogramming. They are used to reduce code repetition and to provide more expressive syntax.
Types of Macros
- Declarative Macros (macro_rules!)
- Procedural Macros
Declarative Macros
Declarative macros are defined using the macro_rules!
keyword. They allow you to match against patterns and generate code based on those patterns.
Example
macro_rules! say_hello { () => { println!("Hello, world!"); }; } fn main() { say_hello!(); }
Procedural Macros
Procedural macros are more flexible and powerful. They are defined using functions and can be used to create attribute-like macros and function-like macros.
Function-like Macros
Function-like macros look like function calls but operate on the code passed to them as arguments. They are defined using the #[proc_macro]
attribute and can be used to generate code based on the input provided.
Example
To create a procedural macro, you need to create a new crate with the proc-macro
attribute:
#![allow(unused)] fn main() { // In your Cargo.toml [lib] proc-macro = true }
Then, define the macro in your crate:
#![allow(unused)] fn main() { extern crate proc_macro; use proc_macro::TokenStream; #[proc_macro] pub fn my_function_like_macro(input: TokenStream) -> TokenStream { // Your macro implementation here input } }
And use it in your main crate:
#![allow(unused)] fn main() { use my_function_like_macro::my_function_like_macro; my_function_like_macro! { // Your code here } }
In this example, my_function_like_macro
is a function-like macro that can be invoked with the !
syntax. The macro processes the input token stream and generates the corresponding code.
Attribute-like Macros
Attribute-like macros are used to create custom attributes that can be applied to items such as functions, structs, or modules. They are defined similarly to procedural macros.
Example
First, create a new crate with the proc-macro
attribute:
#![allow(unused)] fn main() { // In your Cargo.toml [lib] proc-macro = true }
Then, define the attribute-like macro in your crate:
#![allow(unused)] fn main() { extern crate proc_macro; use proc_macro::TokenStream; #[proc_macro_attribute] pub fn my_attribute(_attr: TokenStream, item: TokenStream) -> TokenStream { // Your macro implementation here item } }
And use it in your main crate:
use my_attribute::my_attribute; #[my_attribute] fn my_function() { println!("This function has a custom attribute!"); } fn main() { my_function(); }
In this example, my_attribute
is an attribute-like macro that can be applied to a function. When my_function
is called, it will print a message to the console.
Usage
Macros are invoked using the !
syntax. For example:
#![allow(unused)] fn main() { println!("Hello, {}!", "world"); }
In this example, println!
is a macro that prints formatted text to the console.