(              more productive -  Rust        )

macros!

for<   >

'a

>

'a

<'a>

about me

               (jam, he/him)

macros

macros

macro_rules! name {
    ($pat:expr) => {
        // expansion
        let x = 3;
    }
}

macros

macros

macro_rules! my_macro {
    ($num:expr) => {
        // expansion
        let x = $pat;
    }
}

macros

macros

macro_rules! my_macro {
    ($num:expr) => {
        // expansion
        let x = $num;
        println!("{}", x);
    }
}

fn main() {
    my_macro!(3);
}

















fragment
specifier
description examples
expr expression 3, x, func(x + 1), if x { 1 } else { 2 }, loop {}
ident identifier x, main, my_macro
path path x, main, module::Type, ::crate::Type, x::<T>::y, <T>::foo
ty type MyStruct<'a, T>, WorkerUnion
item top-level item extern "C" fn foo(); use std::fs;
struct Test;
tt token tree fn, main, (), (3), !
block block {}, { 3 }, { let x = 3; }
... ... ...

macros

macros

macro_rules! my_macro {
    ($num:expr) => {
        // expansion
        let x = $num;
        println!("{}", x);
    }
}

fn main() {
    let x = 3;
    println!("{}", x);
}

















fragment
specifier
description examples
expr expression 3, x, func(x + 1), if x { 1 } else { 2 }, loop {}
ident identifier x, main, my_macro
path path x, main, module::Type, ::crate::Type, x::<T>::y, <T>::foo
ty type MyStruct<'a, T>, WorkerUnion
item top-level item extern "C" fn foo(); use std::fs;
struct Test;
tt token tree fn, main, (), (3), !
block block {}, { 3 }, { let x = 3; }
... ... ...
macro_rules! my_macro {
    ($($num:expr),*) => {
        // expansion
        $(
            let x = $pat;
            println!("{}", x);
        )*
    }
}

fn main() {
    my_macro!(3, 4, 5, 6);
}

















macro_rules! my_macro {
    ($num:expr) => {
        // expansion
        let x = $num;
        println!("{}", x);
    }
}

fn main() {
    my_macro!(3);
}

















macro_rules! my_macro {
    ($($num:expr),*) => {
        $(
            let x = $num;
            println!("{}", x);
        )*
    }
}

fn main() {
    my_macro!(3, 4, 5, 6);
}


// $(),* | comma-separated repetition
//       |     (0 or more)
//       |
// $()|+ | pipe-separated repetition
//       |     (1 or more)
//       |
// $()?  | optional (none or once)













macro_rules! my_macro {
    ($num:expr) => {
        // expansion
        let x = $pat;
        println!("{}", x);
    }
}

fn main() {
    my_macro!(3);
}

















macro_rules! my_macro {
    ($num:expr) => {
        // expansion
        let x = $num;
        println!("{}", x);
    }
}

fn main() {
    my_macro!(3);
}

















macro_rules! my_macro {
    ($($num:expr),*) => {
        $(
            let x = $num;
            println!("{}", x);
        )*
    }
}

fn main() {
    my_macro!(3, 4, 5, 6);
}


// $(),* | comma-separated repetition
//       |     (0 or more)
//       |
// $()|+ | pipe-separated repetition
//       |     (1 or more)
//       |
// $()?  | optional (none or once)













macro_rules! my_macro {
    (2) => {
        "Company" // expansion
    }
    (3) => {
        "A Crowd" // expansion
    }
}

let group = my_macro!(3);


















putting
it
all
together

putting
it
all
together

(and to use)

trait AsBytes {
    fn as_bytes(&self) -> Vec<u8>;
}

Implementing

Traits  

trait AsBytes {
    fn as_bytes(&self) -> Vec<u8>;
}

impl AsBytes for u16 {
    fn as_bytes(&self) -> Vec<u8> {
        Vec::from(u16::to_ne_bytes(*self))
    }
}

Implementing

Traits  

trait AsBytes {
    fn as_bytes(&self) -> Vec<u8>;
}

macro_rules! impl_as_bytes {
    () => {
impl AsBytes for u16 {
    fn as_bytes(&self) -> Vec<u8> {
        Vec::from(u16::to_ne_bytes(*self))
    }
}
    }
    
}

Implementing

Traits  

trait AsBytes {
    fn as_bytes(&self) -> Vec<u8>;
}

macro_rules! impl_as_bytes {
    () => {
    	$(
            impl AsBytes for u16 {
                fn as_bytes(&self) -> Vec<u8> {
                    Vec::from(u16::to_ne_bytes(*self))
                }
            }
        )*
    }
    
}

Implementing

Traits  

trait AsBytes {
    fn as_bytes(&self) -> Vec<u8>;
}

macro_rules! impl_as_bytes {
    ($($ty:ty),*) => {
    	$(
            impl AsBytes for $ty {
                fn as_bytes(&self) -> Vec<u8> {
                    Vec::from($ty::to_ne_bytes(*self))
                }
            }
        )*
    }
    
}

Implementing

Traits  

trait AsBytes {
    fn as_bytes(&self) -> Vec<u8>;
}

macro_rules! impl_as_bytes {
    ($($ty:ty),*) => {
    	$(
            impl AsBytes for $ty {
                fn as_bytes(&self) -> Vec<u8> {
                    Vec::from($ty::to_ne_bytes(*self))
                }
            }
        )*
    }
    
}

impl_as_bytes!(u8, i8, u16, i16, u32, i32, u64, i64);

Implementing

Traits  

Internal Rules

Important Techniques

mod macros {
    #[macro_export]
    macro_rules! macro1 {
        () => { macro2!(); }
    }
    
    macro_rules! macro2 {
        () => { /* ... */ }
    }
}

fn main() {
    // error: cannot find macro `macro2` in this scope
    macro1!(); 
}

Internal Rules

Important Techniques

mod macros {
    #[macro_export]
    macro_rules! macro1 {
        () => { macro1!(@macro2); };
        (@macro2) => { /* ... */ }
    }
}

fn main() {
    macro1!(); 
}

Internal Rules

Important Techniques

mod macros {
    #[macro_export]
    macro_rules! macro1 {
        () => { macro1!(@macro2); };
        (@macro2) => { /* ... */ }
    }
}

fn main() {
    macro1!(); 
}

TT Munchers

macro_rules! handle_operations {
    (up $($tt:tt)*) => {
        /* ... */
        handle_operations!($($tt)*);
    };
    (down $($tt:tt)*) => {
        /* ... */
        handle_operations!($($tt)*);
    };
    () => {}
}

handle_operations!(up up down down);

See https://danielkeep.github.io/tlborm/book/ for more useful patterns

macro_rules! handle_operations {
    (up $($tt:tt)*) => {
        /* ... */
        handle_operations!($($tt)*);
    };
    (down $($tt:tt)*) => {
        /* ... */
        handle_operations!($($tt)*);
    };
    () => {}
}

handle_operations!(up up down down);

$($tt:tt)*macro-speak for "everything else"

Important Techniques

TT Munchers

macro_rules! handle_operations {
    (up $($tt:tt)*) => {
        push("up");
        handle_operations!($($tt)*);
    };
    (down $($tt:tt)*) => {
        push("down");
        handle_operations!($($tt)*);
    };
    () => {}
}

handle_operations!(up up down down);

See https://danielkeep.github.io/tlborm/book/ for more useful patterns

$($tt:tt)*macro-speak for "everything else"

handle_operations!(up up down down);

// expands to
push("up");
handle_operations!(up down down);

// expands to
push("up");
push("up");
handle_operations!(down down);

// expands to
push("up");
push("up");
push("down");
handle_operations!(down);

// expands to
push("up");
push("up");
push("down");
push("down");
handle_operations!();

// expands to
push("up");
push("up");
push("down");
push("down");

Paste

use paste::paste;

paste! {
    // Defines a const called `QRST`.
    const [<Q R S T>]: &str = "success!";
}

fn main() {
    assert_eq!(
        paste! { [<Q R S T>].len() },
        8,
    );
}

Proc Macros

Proc Macros

let code = 200;
let features = vec!["content", "json"];

let value = json!({
   "code": code,
   "success": code == 200,
   "payload": {
       features[0]: features[1]
   }
});
#[get("/hello/<name>/<age>")]
fn hello(name: String, age: u8) -> String {
    format!("Hello, {} year old named {}!", age, name)
}
#[derive(Serialize, Deserialize, Debug)]
struct Point {
    x: i32,
    y: i32,
}

let point = Point { x: 1, y: 2 };
let serialized = serde_json::to_string(&point).unwrap();

assert_eq!(
    serialized,
    "{\"x\":1,\"y\":2}"
);

Function-like Macros

Attribute Macros

Derive Macros

Function-like Macros

Attribute Macros

Derive Macros

#[proc_macro]
fn my_proc_macro(input: TokenStream) -> TokenStream {
#[proc_macro_attribute]
fn my_proc_macro(attribute: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_derive(MyTrait, attributes(custom_attribute))]
pub fn derive_my_trait(item: TokenStream) -> TokenStream {
# Cargo.toml
[lib]
proc-macro = true

Creating proc macros

Creating proc macros (cont.)

Crates to know:

  • syn (parsing)
  • quote (code generation)
  • darling (derive macro parsing)
use quote::quote;
use proc_macro::TokenStream;

#[proc_macro_attribute]
pub fn my_proc_macro(_ : TokenStream, input: TokenStream) -> TokenStream {
    let mut my_func = syn::parse_macro_input!(input as syn::ItemFn);
    let func_name = my_func.sig.ident.to_string();

    my_func.block.stmts.insert(
        0,
        syn::parse_quote!(
            println!("Entered function {}", #func_name);
        )
    );

    quote!(
        #my_func
    ).into()
}

The Power of Macros and Lessons Learned

The Power of Macros and Lessons Learned

#[derive(Node)]
pub struct Fighter {
    pub name:    String,
    pub actions: ContextVec<ActionDef>,
    // ...
}

#[derive(Node)]
pub struct ActionDef {
    pub frames: ContextVec<ActionFrame>,
    pub iasa:   i64,
}

#[derive(Node)]
pub struct ActionFrame {
    pub ecb:      ECB,
    pub colboxes: ContextVec<CollisionBox>,
    // ...
}

#[derive(Node)]
pub struct CollisionBox {
    pub point:  (f32, f32),
    pub radius: f32,
    pub role:   CollisionBoxRole,
}

The Power of Macros and Lessons Learned

#[derive(BinRead)]
#[br(magic = b"DOG", assert(name.len() != 0))]
struct Dog {
    bone_pile_count: u8,
 
    #[br(big, count = bone_pile_count)]
    bone_piles: Vec<u16>,
 
    #[br(align_before = 0xA)]
    name: NullString
}

// 44 4f 47             | DOG
// 02 00                | ..
// 01 00                | ..
// 12 00 00             | ...
// 50 69 65 72 72 65 00 | Pierre.

The Power of Macros and Lessons Learned

#[derive(BinRead)]
#[br(magic = b"DOG", assert(name.len() != 0))]
struct Dog {
    bone_pile_count: u8,
 
    #[br(big, count = bone_pile_count)]
    bone_piles: Vec<u16>,
 
    #[br(align_before = 0xA)]
    name: NullString
}

// 44 4f 47             | DOG
// 02 00                | ..
// 01 00                | ..
// 12 00 00             | ...
// 50 69 65 72 72 65 00 | Pierre.

The Power of Macros and Lessons Learned

#[acmd_func(
    battle_object_category = BATTLE_OBJECT_CATEGORY_FIGHTER, 
    battle_object_kind = FIGHTER_KIND_MARIO, 
    animation = "attack_air_f",
    animcmd = "game_attackairf")]
pub fn mario_fair(fighter: &mut L2CFighterCommon) {
    acmd!({
        frame(16)
        if(is_execute) {            
            ATTACK(ID=0, Part=0, Bone=hash40("arml"), Damage=19.0, Angle=361, KBG=80, FKB=0, BKB=30, Size=113.0, X=3.2, Y=0.0, Z=0.0, X2=LUA_VOID, Y2=LUA_VOID, Z2=LUA_VOID, Hitlag=1.0, SDI=1.0, Clang_Rebound=ATTACK_SETOFF_KIND_ON, FacingRestrict=ATTACK_LR_CHECK_F, SetWeight=false, ShieldDamage=0, Trip=0.0, Rehit=0, Reflectable=false, Absorbable=false, Flinchless=false, DisableHitlag=false, Direct_Hitbox=true, Ground_or_Air=COLLISION_SITUATION_MASK_GA, Hitbits=COLLISION_CATEGORY_MASK_ALL, CollisionPart=COLLISION_PART_MASK_ALL, FriendlyFire=false, Effect=hash40("collision_attr_normal"), SFXLevel=ATTACK_SOUND_LEVEL_M, SFXType=COLLISION_SOUND_ATTR_PUNCH, Type=ATTACK_REGION_PUNCH)
            
            // Inline Rust code inside ACMD macro
            rust {
                println!("Fair frame 16");
            }

            sv_kinetic_energy::add_speed(1.0);
        }
    });
}
use inline_python::python;

fn main() {
    let who = "world";
    let n = 5;
    python! {
        for i in range('n):
            print(i, "Hello", 'who)
        print("Goodbye")
    }
}

The Power of Macros and Lessons Learned

#[skyline::hook(offset = 0x7709f0)]
pub fn wild_initialize(unk: u64, pokemon: &mut WildPokemon) {
    let personal_data = PersonalData::get_instance();
    let mut rng = rand::thread_rng();

    pokemon.species_id = rng.gen_range(0, SPECIES_COUNT as u32);

    let gender = personal_data.get(pokemon.species_id).unwrap().gender;
    pokemon.gender = match gender {
        0 => 0, // Male only
        0xFE => 1,// Female only
        0xFF => 2, // Genderless
        _ => rng.gen_range(0, 2)
    };
    
    // ...

    original!()(unk, pokemon);
}

Credit: Raytwo, DeathChaos25

https://github.com/Raytwo/swsh_randomizer

But can we go further...?

Features this "good language" doesn't have:

  • C++ interoperability
  • Inheritance                                                 
  • etc.           

 

Conclusion: Rust is a bad language.

(kidding)

Introducing: cpp-inherit

"A macro for inheriting Rust structures from C++ classes. Nothing valued is here."

use cpp_inherit::*;

#[inherit_from(BaseType)]
#[derive(Debug)]
struct RustType {}

#[inherit_from_impl(BaseType, "test.hpp")]
impl RustType {
    #[overridden] fn x(&self) -> i32 {
        99
    }
}

extern "C" {
    /// Calls virtual method "x" on a BaseType*
    fn call_x_on(base: &mut BaseType);
}

fn main() {
    let var = RustType::new();
    call_x_on(&mut var);
}

RustConf 2020: Macros for a More Productive Rust

By jam1garner

RustConf 2020: Macros for a More Productive Rust

  • 1,262