( 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,
}
Credit: Lucas Kent
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
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