4 ways to create macros in rust
macro_rules!
#[proc_macro_derive(Name)]
#[proc_macro_attribute]
#[proc_macro]
#![ feature( trace_macros) ]
#![ feature( log_syntax) ]
#[ macro_use]
mod my_vec;
#[ macro_use]
mod greeting;
use crate :: greeting:: base_greeting_fn;
#[ macro_use]
mod generate_get_value;
use crate :: account_dsl:: Account ;
#[ macro_use]
mod account_dsl;
#[ macro_use]
mod recursive_compose;
use crate :: recursive_compose:: compose_two;
fn main ( ) {
custom_vec ( ) ;
variadic_greeting ( ) ;
recursive_newtype:: create ( ) ;
use_account_dsl ( ) ;
compose_vector_of_fn ( ) ;
}
trace_macros ! ( true ) ;
[dependencies ]
procedural-basic-macro = { path = " ./procedural-basic-macro" }
#[ macro_use]
extern crate procedural_basic_macro;
#[ derive( Hello ) ]
struct Example ;
#[ derive( Hello ) ]
enum Pet {
Cat ,
}
fn main ( ) {
let e = Example { } ;
e. hello_world ( ) ;
let c = Pet :: Cat ;
c. hello_world ( ) ;
}
[dependencies ]
quote = " 1.0.36"
syn = " 2.0.63"
[lib ]
proc-macro = true
use proc_macro:: TokenStream ;
use quote:: quote;
use syn:: { parse_macro_input, DeriveInput } ;
#[ proc_macro_derive( Hello ) ]
pub fn hello ( item : TokenStream ) -> TokenStream {
let ast = parse_macro_input ! ( item as DeriveInput ) ;
let name = ast. ident ;
let add_hello_world = quote ! {
impl #name {
fn hello_world( & self ) {
println!( "Hello" , stringify!( #name) )
}
}
} ;
add_hello_world. into ( )
}
Procedural Attribute Macros
extern crate core;
use proc_macro:: TokenStream ;
use quote:: { quote, ToTokens } ;
use syn:: parse:: { Parse , ParseStream } ;
use syn:: punctuated:: Punctuated ;
use syn:: token:: Colon ;
use syn:: Data :: Struct ;
use syn:: Fields :: Named ;
use syn:: { parse_macro_input, DataStruct , DeriveInput , Field , FieldsNamed , Ident , Type , Visibility } ;
impl StructField {
fn new ( field : & Field ) -> Self { ... }
}
impl Parse for StructField {
fn parse ( input : ParseStream ) -> Result < Self , syn:: Error > { ... }
}
impl ToTokens for StructField {
fn to_tokens ( & self , tokens : & mut proc_macro2:: TokenStream ) { .... }
}
#[ proc_macro_attribute]
pub fn xxx ( attr : TokenStream , item : TokenStream ) -> TokenStream { ... }
let ast = parse_macro_input ! ( item as DeriveInput ) ;
eprintln ! ( "{:#?}" , & ast) ;
Procedural Function Macros
[dependencies ]
make-private-macro = { path = " ./make-private-macro" }
function-like-compose-macro = { path = " ./function-like-compose-macro" }
use proc_macro:: TokenStream ;
use quote:: quote;
use syn:: Data :: Struct ;
use syn:: Fields :: Named ;
use syn:: __private:: { Span , TokenStream2 } ;
use syn:: { parse_macro_input, DeriveInput , Type } ;
use syn:: { DataStruct , FieldsNamed , Ident } ;
fn get_field_info ( ast : & DeriveInput ) -> Vec < ( & Ident , & Type ) > { ... }
fn generated_methods ( fields : & Vec < ( & Ident , & Type ) > ) -> Vec < TokenStream2 > { ... }
fn generate_private_fields ( fields : & Vec < ( & Ident , & Type ) > ) -> Vec < TokenStream2 > { ... }
#[ proc_macro]
pub fn private ( item : TokenStream ) -> TokenStream { ... }
use proc_macro:: TokenStream ;
use quote:: { quote, ToTokens } ;
use syn:: parse:: { Parse , ParseStream } ;
use syn:: punctuated:: Punctuated ;
use syn:: { Ident , parse_macro_input, Token } ;
struct ComposeInput {
expressions : Punctuated < Ident , Token ! ( . ) > ,
}
impl Parse for ComposeInput {
fn parse ( input : ParseStream ) -> Result < Self , syn:: Error > { ... }
}
impl ToTokens for ComposeInput {
fn to_tokens ( & self , tokens : & mut proc_macro2:: TokenStream ) { ... }
}
#[ proc_macro]
pub fn compose ( item : TokenStream ) -> TokenStream {
let ci: ComposeInput = parse_macro_input ! ( item) ;
quote ! (
{
...
#ci
}
) . into ( )
}
Builder pattern + Testing
[workspace ]
resolver = " 2"
members = [
" builder-macro" ,
" builder-code" ,
" builder-usage"
]
[dependencies ]
builder-macro = { path = " ../builder-macro" }
[dev-dependencies ]
trybuild = " 1.0.96"
use builder_macro:: Builder ;
fn main ( ) { }
#[ cfg( test) ]
mod tests {
use super :: * ;
#[ test]
fn should_generate_builder_for_struct_with_no_properties ( ) { ... }
#[ test]
fn should_generate_builder_for_struct_with_one_property ( ) { ... }
#[ test]
fn should_generate_builder_for_struct_with_two_properties ( ) { ... }
#[ test]
fn should_generate_builder_for_struct_with_multiple_properties ( ) { ... }
#[ test]
#[ should_panic]
fn should_panic_when_field_is_missing ( ) { ... }
}
#[ test]
fn should_not_compile ( ) {
let t = trybuild:: TestCases :: new ( ) ;
t. compile_fail ( "tests/fails/*.rs" ) ;
}
[dependencies ]
builder-code = { path = " ../builder-code" }
[lib ]
proc-macro = true
use builder_code:: create_builder;
use proc_macro:: TokenStream ;
#[ proc_macro_derive( Builder ) ]
pub fn builder ( item : TokenStream ) -> TokenStream {
create_builder ( item. into ( ) ) . into ( )
}
[dependencies ]
proc-macro2 = " 1.0.82"
quote = " 1.0.36"
syn = { version = " 2.0.64" , features = [" extra-traits" ] }
use quote:: quote;
use syn:: __private:: TokenStream2 ;
use syn:: punctuated:: Punctuated ;
use syn:: token:: Comma ;
use syn:: { Field , Ident , Type } ;
fn get_name_and_type < ' a> ( f : & ' a Field ) -> ( & ' a Option < Ident > , & ' a Type ) { ... }
pub fn builder_field_definitions ( fields : & Punctuated < Field , Comma > , ) -> impl Iterator < Item = TokenStream2 > + ' _ { ... }
pub fn original_struct_setters ( fields : & Punctuated < Field , Comma > , ) -> impl Iterator < Item = TokenStream2 > + ' _ { ... }
pub fn builder_methods ( fields : & Punctuated < Field , Comma > , ) -> impl Iterator < Item = TokenStream2 > + ' _ { ... }
pub fn builder_init_values ( fields : & Punctuated < Field , Comma > , ) -> impl Iterator < Item = TokenStream2 > + ' _ { ... }
#[ cfg( test) ]
mod tests {
use proc_macro2:: Span ;
use syn:: { FieldMutability , Path , PathSegment , TypePath , Visibility } ;
use super :: * ;
#[ test]
fn get_name_and_type_give_back_name ( ) { ... }
}
use proc_macro2:: TokenStream ;
use quote:: { format_ident, quote} ;
use syn:: Data :: Struct ;
use syn:: DeriveInput ;
use syn:: Fields :: Named ;
use syn:: { DataStruct , FieldsNamed , Ident } ;
mod fields;
use fields:: {
builder_field_definitions, builder_init_values, builder_methods, original_struct_setters,
} ;
pub fn create_builder ( item : TokenStream ) -> TokenStream { ... }
#[ cfg( test) ]
mod tests {
use super :: * ;
#[ test]
fn builder_struct_name_should_be_present_in_output ( ) { ... }
#[ test]
fn builder_struct_with_expected_methods_should_be_present_in_output ( ) { ... }
}
function signature modification - replace panic with results
cargo add syn --features "full extra-traits"
[dependencies ]
proc-macro-error = " 1.0.4"
proc-macro2 = " 1.0.83"
quote = " 1.0.36"
syn = { version = " 2.0.66" , features = [" full" , " extra-traits" ] }
[lib ]
proc-macro = true
use proc_macro:: TokenStream ;
use quote:: { quote, ToTokens } ;
use syn:: { token:: Semi , Expr , ItemFn , ReturnType , Stmt , StmtMacro , Visibility } ;
use syn:: spanned:: Spanned ;
use proc_macro_error:: proc_macro_error;
use proc_macro_error:: emit_error;
fn signature_output_as_result ( ast : & ItemFn ) -> ReturnType { ... }
fn last_statement_as_result ( last_statement : Option < Stmt > ) -> Stmt { ... }
fn extract_panic_content ( expr_macro : & StmtMacro ) -> Option < proc_macro2:: TokenStream > { ... }
fn handle_expression ( expression : Expr , token : Option < Semi > ) -> Stmt { ... } // tricky refactors - change to Result, then change back ...
#[ proc_macro_error]
#[ proc_macro_attribute]
pub fn panic_to_result ( _attr : TokenStream , item : TokenStream ) -> TokenStream { ... }
unfortunately, my error messages were not exact matches ... :(
create_person_two_issues.rs
+ create_person_two_issues.stderr
create_person_with_empty_panic.rs
+ create_person_with_empty_panic.stderr
create_person_with_result.rs
+ create_person_with_result.stderr