In the world of Rust, macros are a powerful tool that allow you to write code that writes other code. This capability is particularly useful for code generation and abstraction, helping developers reduce boilerplate and increase productivity. In this tutorial, we will dive into procedural macros, which are a type of macro that allows you to generate code at compile time based on the input tokens.
Procedural macros are functions that take Rust syntax trees as input and produce new Rust syntax trees as output. They are defined using the proc_macro crate, which provides the necessary tools for parsing and manipulating Rust syntax trees. Procedural macros can be categorized into three types:
In this tutorial, we will focus on writing a simple function-like procedural macro that generates a function based on its input.
First, you need to set up a new Rust project with the necessary dependencies. Open your terminal and run:
$ cargo new --lib my_macro_project$ cd my_macro_project$ cargo add proc-macro-hack$ cargo add quote$ cargo add syn
Now, let's create a procedural macro that generates a function. We'll start by defining the macro in src/lib.rs.
1use proc_macro::TokenStream;2use quote::quote;3use syn::{parse_macro_input, ItemFn};45#[proc_macro]6pub fn generate_function(input: TokenStream) -> TokenStream {7// Parse the input tokens into a syntax tree8let func = parse_macro_input!(input as ItemFn);910// Generate new function name by appending "_generated" to the original function name11let new_func_name = &func.sig.ident;12let new_func_ident = format!("{}_generated", new_func_name);13let new_func_ident = syn::Ident::new(&new_func_ident, func.span());1415// Generate the new function body16let expanded = quote! {17fn #new_func_ident() {18println!("Generated function called!");19}20};2122TokenStream::from(expanded)23}
Next, we'll use our macro in another file. Create a new file src/main.rs and add the following code:
1extern crate my_macro_project;23use my_macro_project::generate_function;45// Define a function that will be used as input for the macro6fn example_function() {7println!("Original function called!");8}910// Use the procedural macro to generate a new function11generate_function!(example_function);1213fn main() {14// Call both functions to see the output15example_function();16example_function_generated();17}
Compile and run your project using Cargo:
$ cargo build --release$ ./target/release/my_macro_projectOriginal function called!Generated function called!
You should see the output from both the original and generated functions, demonstrating that our procedural macro successfully created a new function based on the input.
In this tutorial, we covered the basics of writing procedural macros for code generation. In the next section, we will explore attribute macros, which allow you to modify the behavior of items like functions or structs. Understanding both types of macros will give you powerful tools for enhancing your Rust projects with compile-time code generation and abstraction.
Info