# Macros `println!` `vec!` are Macros, of which there are `Declarative` and `Procedural` ## Macros vs Functions Macros specify code that write instead other code, similar to a `type othertype: actual_type`, that is called *metaprogramming*. `#[derive()]` is actually also a *metaprogramming* element that "writes" or implements things for the type. Functions require a set amount of parameters, macros do not. Macros can implement a trait for a type, functions can't because it is checked at compile time and functions are checked at runtime. Macros are more complex to write, they are code that generates more code! Macros must be brought into scope before called! ## Declarative Macros with `macro_rules!` for General Metaprogramming *declarative macros* are also called *macros by example* *`macro_rules` macros* or just *macros*. They evaluate the pattern passed by the user, then generate code according to the pattern, all at compile time. ```rust let v: Vec = vec![1,2,3]; // Simplified vec! implementation #[macro_export] macro_rules! vec { ( $( $x:expr ),* ) => { { let mut temp_vec = Vec::new(); $( temp_vec.push($x); )* temp_vec } }; } ``` - `#[macro_exeport]` -> Indicates this will be imported by anyone that uses the crate it is defined in. - `macro_rules! _name_` -> Declaration - `( $( $x:expr ),* )` -> expression to evaluate - `=> {...}` -> Block of code to execute if the pattern matches ### Syntax Breakdown - `$()` -> Capture a Value - `$x: expr` -> Any expression type, then given name `x` - `,` -> After $() the comma means that we could have more comming after - `*` -> After `,` means that what comes after is the same type as previously matched expression - `=> {` -> After generating the expression, we will now execute the block of code - In the block of code, we replace with a call to `Vec::new()` to initialize the vector - `$( ... $x ... )` -> For each value that matched the expression, we replace with the block that makes use of the matched expression/s ```rust vec![1,2,3] -> ( $($x: 1), 2, 3) // What is interpreted let mut temp_vec = Vec::new(); temp_vec.push(1); temp_vec.push(2); temp_vec.push(2); temp_vec ``` For further info on writing macros: - https://veykril.github.io/tlborm/ - https://doc.rust-lang.org/reference/macros-by-example.html ## Procedural Macros for Generating Code from Attributes They are more similar to functions, that accept an input, operate on that input and generate some code. They are complex, and also they are required to be in their own named crate! There are 3 kinds: - Custom `#[derive]` that specify code added with the `derive` attirbute - Attribute-like macros that define custom attributes usable on any item - Funciton-like macros that look like functions, but use some specific items specified as arguments Using the following Article: https://blog.logrocket.com/procedural-macros-in-rust/ They use an `Abstract Syntax Tree` to make use of a `TokenStream` or 2, to output another `TokenStream`. In other words, the inputs are the code that is inspected and the output are the code generated. ```rust use proc_macro; // Crate needed to use them // [lib] proc-macro = true // in Cargo.toml #[some_attribute] pub fn some_name(input: TokenStream) -> TokenStream { } ``` ## Custom `derive` Macro Assume the following binary code: ```rust // Using crate hello_macro use hello_macro::HelloMacro; // Defines HelloMacro trait use hello_macro_derive::HelloMacro; // Crate that defines the derivation of HelloMacro trait, to help users avoid the writing of the implementation block for the Trait #[derive(HelloMacro)] // Derives the Trait struct Pancakes; // No implementation block is required! fn main() { Pancakes::hello_macro(); } // hello_macro will print "Hello, Macro! My Name is TypeName!" where TypeName is the type which derives HelloMacro ``` We would first define the library crate that holds the crate `hello_macro`: ```rust //cargo new hello_macro --lib //lib.rs pub trait HelloMacro { fn hello_macro(); } // We can't provide the definition, because we would not know yet the type! ``` Then the `hello_macro_derive` library crate that holds the procedural macro inside the `hello_macro` crate: ```rust // cargo new hello_macro_derive --lib // cargo.toml - add the following [lib] proc-macro = true [dependencies] # // Dependencies just needed for this example syn = "1.0" # // To parse Rust Code form a string into a Data Structure quote = "1.0" # // Turn `syn` Data Structure into Rust Code // lib.rs extern crate proc_macro; use proc_macro::TokenStream; use quote::quote; use syn; #[proc_macro_derive(HelloMacro)] pub fn hello_macro_derive(input: TokenStream) -> TokenStream { // Construct a representation of Rust code as a syntax tree // that we can manipulate let ast = syn::parse(input).unwrap(); // Returns DeriveInput struct, which is the parsed Rust Code // Use unwrap for simplicity, .parse() returns a `Result` which should be handled carefully // Build the trait implementation impl_hello_macro(&ast) // What actually transforms the AST into another block of code } // Calling #[derive(HelloMacro)] will actually call the linked function with #[proc_macro_derive(HelloMacro)] which will be hello_macro_derive() ``` ```rust // Example of the DeriveInput struct resulting from the `Pancakes` type DeriveInput { // --snip-- ident: Ident { ident: "Pancakes", span: #0 bytes(95..103) }, data: Struct( DataStruct { struct_token: Struct, fields: Unit, semi_token: Some( Semi ) } ) } ``` Understanding how the `Type` is deconstructed is vital to writing how to interpret it and then generate code accordingly: ```rust // We take a `DeriveInput` as parameter, return a TokenStream fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; // Get the string slice of the name! let gen = quote! { //quote! Allows us to store a block of Code! impl HelloMacro for #name { // # means replace for the value in a variable, in the context of a `quote!` fn hello_macro() { println!("Hello, Macro! My name is {}!", stringify!(#name)); // another use of # // stringify! takes an expressiong and turns it into a string literal } } }; gen.into() // .into() transforms the stored block of code into the Required type, in this case a TokenStream } ``` More on `quote!`: https://docs.rs/quote/latest/quote/ Holy shit this is incredibly powerful metaprogramming syntax. It is clunky to manage tho. ## Atrribute-like Macros Similar to `Custom derive Macros`, but instead of using the `derive` attribute you can create new attributes! `derive` works for struct/enum only. Attributes can be applied to more types, for example `#[proc_macro_derive]` is an attribute applioed to a function! Example: ```rust #[route(GET, "/")] fn index(); // implementation... #[proc_macro_attribute] pub fn rout(attr: TokenStream, item: TokenStream) -> TokenStream { } ``` We have 2 parameters `TokenStream`. 1st the contests `Get, "/"`, then the block of code `fn index() {...}` After that initial definition, they work the same way as `Custom Derive Macros`, transform TokenStream into a readable `syn` Data Struct, operate on it and return back another `TokenStream` which will be the generated code. ## Function-like Macros Similar to `macro_rules!` macros, more flexible than functions. Take a `TokenStream` and operate over it and return another `TokenStream`: ```rust let sql = sql!(SELECT * FROM posts WHERE id=1); //implementation... #[proc_macro] pub fn sql(inputL TokenStream) -> TokenStream { // Parse SQL code } ``` Basically the same as a `Custom Derive Macro` but they are syntactically used like a `macro_rules!` syntax in the flair of `function_name!(TokenStream_to_parse)`