Rust macros: Difference between revisions
Created page with "=Macros= I did macros using YouTube and the course. The first example is the course, the second is YouTube. ==Greatest Common Denominator Macro== Here is the simple example. <syntaxhighlight lang="rs"> macro_rules! gcd { ($a: expr, $b: expr) => { { let mut a = $a; let mut b = $b; while b != 0 { let t = b; b = a % b; a = t; } a } }; } mai..." |
|||
Line 1: | Line 1: | ||
=Macros= | =Macros= | ||
I did macros using YouTube and the course. The first example is the course, the second is YouTube. | I did macros using YouTube and the course. The first example is the course, the second is YouTube. | ||
==Exporting a macro== | |||
By default macros are scoped to their module. To be able to export them you need to add #[macro_use] otherwise you will see '''cannot find macro `my_macro` in this scope''' | |||
<syntaxhighlight lang="rs"> | |||
#[macro_use] | |||
mod macros { | |||
macro_rules! my_macro { | |||
() => { | |||
println!("Check out my macro!"); | |||
}; | |||
} | |||
} | |||
</syntaxhighlight> | |||
==Greatest Common Denominator Macro== | ==Greatest Common Denominator Macro== | ||
Here is the simple example. | Here is the simple example. | ||
Line 24: | Line 37: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==Declarative Macros== | ==Declarative Macros== |
Latest revision as of 04:54, 15 October 2024
Macros
I did macros using YouTube and the course. The first example is the course, the second is YouTube.
Exporting a macro
By default macros are scoped to their module. To be able to export them you need to add #[macro_use] otherwise you will see cannot find macro `my_macro` in this scope
#[macro_use]
mod macros {
macro_rules! my_macro {
() => {
println!("Check out my macro!");
};
}
}
Greatest Common Denominator Macro
Here is the simple example.
macro_rules! gcd {
($a: expr, $b: expr) => {
{
let mut a = $a;
let mut b = $b;
while b != 0 {
let t = b;
b = a % b;
a = t;
}
a
}
};
}
main() {
print!("gcd is {} ", gcd!(14,28));
}
Declarative Macros
This seems similar to C++ with improvements. Need to learn the syntax but this should be enough for me to get going. Here is a basic macro.
#[macro_export]
macro_rules! my_macro {
() => {
{ // Do Stuff }
};
}
Export allows the macro to be seen, macro_rules! is the syntax to say I am a macro then the name of the macro. We can now write an arrow function similar to typescript. This has curly braces around whatever we are returning.
This example appends a "# " to the text passed. The function arguments are $var and in this case the type is literal. These are known as fragment specifiers. And a list can be found [here]
#[macro_export]
macro_rules! header {
($var:literal) => {
{concat!("# ", $var)}
};
}
fn main() {
let fred = header!("fred");
println!("Hello, world! {}", fred);
}
We can test this using the testing framework offered by rust and running cargo test
#[cfg(test)]
mod tests {
#[test]
fn test_header() {
let val = header!("Hello");
assert_eq!(val, "# Hello");
}
}
A more complicated macro maybe one that takes a list of values. E.g. ulist!["a","b","c"]. First we define a parameter with multiple inputs, to do this start with $(), separator , and repetition operator +. A list of repetition operators may be found [here]
macro_rules! ulist {
($(),+) => {
};
}
To emphasise the separator I have changed it to a % to make it clear how it is used.
#[macro_export]
macro_rules! ulist {
($($var:literal)%+) => {
{ concat!( $("item:", $var, "\n",)+ ).trim_end() }
};
}
Basically the example concatenates the values together and using .trim_end() removes the last "\n". Left it in as it may remind me how to do this. And here is the test.
#[test]
fn test_ulist() {
let val = ulist!("foo"%"bar");
assert_eq!(val, "item:foo\nitem:bar");
}
Procedural Macros
The previous macros were like C++ macros which match against patterns. Procedural macros are like functions, they take code as input, operate on it and then output code. There are three types of these
- Custom #[derive] macros.
- Attribute-like macros.
- Function-like macros
We are going to look at the derived as this is what I have come across and need to know
This type of macro takes code as input and outputs code.
- Create a library crate called billprod_macro
- Create a procedural macro crate inside billprod_macro (billprod_macro_derive)
Macro Library
This just defines the trait we are going to build. In the lib.rs we declare this trait (interface)
pub trait BillProdMacro {
fn bill_prod_macro();
}
Macro Derive Library
This lives inside of the macro library. For the procedural macro we need to define the Cargo.toml as a proc-macro and add dependencies syn and quote.
[package]
name = "billprod_macro_derive"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
syn = "1.0.75"
quote = "1.0.9"
For the code, we have something called TokenStream which is capable of parsing rust code. We write a function to receive the code, do something, and a function to output the code. My understanding is these are the same for most macros. We annotate it with the name of the macro #[proc_macro_derive(BillProdMacro)]
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro_derive(BillProdMacro)]
pub fn billprod_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();
// Build the trait implementation
impl_billprod_macro(&ast)
}
The implementation takes the code (ast) prints out text with the type.
fn impl_billprod_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl BillProdMacro for #name {
fn bill_prod_macro() {
println!("Hello, world! My name is {}", stringify!(#name));
}
}
};
gen.into()
}
Using the Macro
We need both packages. As they are not published we must reference them on disk
...
[dependencies]
billprod_macro = { path = "../billprod_macro" }
billprod_macro_derive = { path = "../billprod_macro/billprod_macro_derive" }
So now we can use them in the code.
use billprod_macro::BillProdMacro;
use billprod_macro_derive::BillProdMacro;
#[derive(BillProdMacro)]
struct BillCake;
....
fn main() {
BillCake::bill_prod_macro();
}
This outputs
Hello, world! My name is BillCake