vs Sliced Bread Alex Crichton Rise of Procedural Macros Jan 2014 - - PowerPoint PPT Presentation

vs
SMART_READER_LITE
LIVE PREVIEW

vs Sliced Bread Alex Crichton Rise of Procedural Macros Jan 2014 - - PowerPoint PPT Presentation

Procedural Macros vs Sliced Bread Alex Crichton Rise of Procedural Macros Jan 2014 - PR #11151 lands, first "loadable syntax extensions" Jan 2014 to present - regret #11151 landing Feb 2016 - RFC 1561, modularizing


slide-1
SLIDE 1

Procedural Macros

Alex Crichton

🍟 Sliced Bread 🥫

vs

slide-2
SLIDE 2

Rise of Procedural Macros

  • Jan 2014 - PR #11151 lands, first "loadable syntax extensions"
  • Jan 2014 to present - regret #11151 landing
  • Feb 2016 - RFC 1561, modularizing macros
  • Feb 2016 - RFC 1566, first proc macro specification
  • July 2016 - RFC 1681, Derive Macros (serde!)
  • Apr 2018 - Call for stabilization of macros other than #[derive]
  • Oct 2018 - Attribute and function-like macros on stable
slide-3
SLIDE 3

Rise of Sliced Bread 🍟

  • 1912 - Otto Frederick Rohwedder, of Iowa, prototypes first

bread slicing machine

  • 1912 - prototype machine destroyed in fire
  • 1928 - Chillicothe Baking Company sells first sliced bread
  • Jan 1943 - US bans sliced bread as a wartime conservation

measure

  • Mar 1943 - ban rescinded, sliced bread too popular
  • 1912 to present - sliced bread still tasty
slide-4
SLIDE 4

Procedural Macros?! Writing Macros Future of Macros

slide-5
SLIDE 5

Macro Forms

println!(...) #[derive(Serialize)] #[wasm_bindgen]

slide-6
SLIDE 6

The proc-macro crate type

[package] name = "my-macro" version = "0.1.0" [lib] proc-macro = true

slide-7
SLIDE 7

println!(...)

#[proc_macro] fn println(input: TokenStream)

  • > TokenStream

{ // ... } println!("wheat"); std::io::print(format_args!("wheat"));

slide-8
SLIDE 8

#[derive(Serialize)]

#[proc_macro_derive(Serialize)] fn derive_ser(input: TokenStream)

  • > TokenStream

{ // ... } #[derive(Serialize)] struct Rye { grains: i32 } impl Serialize for Rye { ... }

slide-9
SLIDE 9

#[wasm_bindgen]

#[proc_macro_attribute] fn wasm_bindgen( args: TokenStream, input: TokenStream, ) -> TokenStream { /* ... */ } #[wasm_bindgen(start)] fn bake() { /* ... */ }

#[no_mangle] #[export_name = "bake"] pub extern fn __wbg_bake() { bake() } fn bake() { /* ... */ }

slide-10
SLIDE 10

TokenStream?

  • Lexical foundation for Rust syntax
  • Provided by proc_macro compiler crate
  • "Rc<Vec<TokenTree>>"
slide-11
SLIDE 11

TokenTree?

enum TokenTree { Group(Group), Ident(Ident), Punct(Punct), Literal(Literal), }

#[derive(Serialize)] #[serde(rename_all = "kebab-case")] struct Sourdough { sour_factor: u32, starter_dynasty: Dynasty, }

slide-12
SLIDE 12

TokenTree?

enum TokenTree { Group(Group), Ident(Ident), Punct(Punct), Literal(Literal), }

#[derive(Serialize)] #[serde(rename_all = "kebab-case")] struct Sourdough { sour_factor: u32, starter_dynasty: Dynasty, }

slide-13
SLIDE 13

TokenTree?

enum TokenTree { Group(Group), Ident(Ident), Punct(Punct), Literal(Literal), }

#[derive(Serialize)] #[serde(rename_all = "kebab-case")] struct Sourdough { sour_factor: u32, starter_dynasty: Dynasty, }

slide-14
SLIDE 14

TokenTree?

enum TokenTree { Group(Group), Ident(Ident), Punct(Punct), Literal(Literal), }

#[derive(Serialize)] #[serde(rename_all = "kebab-case")] struct Sourdough { sour_factor: u32, starter_dynasty: Dynasty, }

slide-15
SLIDE 15

Modularized Macros

use std::println; use serde::Serialize; use wasm_bindgen::prelude::wasm_bindgen; // or... pub use wasm_bindgen_macro::wasm_bindgen;

Brings in trait as a bonus!

slide-16
SLIDE 16

Procedural Macros?! Writing Macros Future of Macros

slide-17
SLIDE 17

#[proc_macro] pub fn println(input: TokenStream)

  • > TokenStream

{ // ??? }

slide-18
SLIDE 18

struct MyInvocation {
 format: String, args: Vec<Expr>, } #[proc_macro] pub fn println(input: TokenStream)

  • > TokenStream

{ let input = MyInvocation::parse(input)?; input.validate()?; input.expand() } println!("{} loaves please", count);

slide-19
SLIDE 19

struct MyInvocation {
 format: String, args: Vec<syn::Expr>, } #[proc_macro] pub fn println(input: TokenStream)

  • > TokenStream

{ let input = MyInvocation::parse(input)?; input.validate()?; input.expand() }

syn = "0.15" quote = "0.6"

println!("{} loaves please", count);

slide-20
SLIDE 20

Parsing with syn

  • Provides parsers for all Rust syntax
  • Easily define custom recursive descent parsers
  • Preserves Span information (hard, but important!)
  • Aggressively feature gated to compile quickly
slide-21
SLIDE 21

Parsing with syn

use syn::{parse_macro_input, DeriveInput}; #[proc_macro_derive(MyMacro)] pub fn my_macro(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); // ... }

slide-22
SLIDE 22

Expanding with quote

  • Only way to create a TokenStream is FromIterator
  • The quote crate provides a quote! macro for quasi

quoting

  • Custom types interpolated with ToTokens trait
slide-23
SLIDE 23

TokenStream::from_iter(vec![ TokenTree::from(Ident::new("let", span)), TokenTree::from(Ident::new("slices", span)), TokenTree::from(Punct::new('=', Spacing::Alone)), TokenTree::from(Literal::u32_unsuffixed(42)), TokenTree::from(Punct::new(';', Spacing::Alone)), ])

vs...

quote! { let slices = 42; }

slide-24
SLIDE 24

Interpolation in quote

let name: Ident = ...; let fields: Vec<TokenStream> = ...; quote! { struct #name { #(#fields),* } impl BakeBread for #name { fn place_in_oven(&self) { // ... } }
 }

slide-25
SLIDE 25

Working with Span

#[bake_at(375)] pub fn knead(bread: &Bread) { let (a, b) = bread.split_in_half(); }

error[E0599]: no method named `split_in_half` found for type `&Bread` in the current scope

  • -> src/main.rs:4:1

| 4 | #[bake_at(375)] | ^^^^^^^^^^^^^^^ error[E0599]: no method named `split_in_half` found for type `&Bread` in the current scope

  • -> src/main.rs:6:24

| 6 | let (a, b) = bread.split_in_half(); | ^^^^^^^^^^^^^

vs

slide-26
SLIDE 26

Working with Span

#[bake_at(375)] pub fn knead(bread: &Bread) { let (a, b) = bread.split_in_half(); }

error: please specify what kind of bread

  • -> src/main.rs:5:22

| 5 | pub fn knead(bread: &Bread) { | ^^^^^

slide-27
SLIDE 27

Procedural Macros?! Writing Macros Future of Macros

slide-28
SLIDE 28

Hygiene

quote! { impl MyTrait for #the_type { // ... } }

slide-29
SLIDE 29

Hygiene

quote! { impl my_crate::MyTrait for #the_type { // ... } }

slide-30
SLIDE 30

Hygiene

quote! { impl ???::MyTrait for #the_type { // ... } }

[dependencies]

  • ther-name = { package = "my-crate", version = "1.0" }
slide-31
SLIDE 31

Diagnostics API

error[E0308]: mismatched types

  • -> src/main.rs:14:10

| 14 | bake(bread); | ^^^^^ | | | expected &Bread, found struct `Bread` | help: consider borrowing here: `&bread` | = note: expected type `&Bread` found type `Bread`

slide-32
SLIDE 32

Debugging Macros

error: expected one of `!` or `::`, found `<eof>`

  • -> src/main.rs:4:1

| 4 | #[bake_at(375)] | ^^^^^^^^^^^^^^^

slide-33
SLIDE 33

Procedural Macro Gallery

https://github.com/dtolnay/remain

#[remain::sorted] pub enum Bread { Focaccia, Rye, Sourdough, Wheat, }

slide-34
SLIDE 34

Procedural Macro Gallery

#[derive(StructOpt)] #[structopt(name = "bake", about = "Bake some bread.")] struct Opt { #[structopt(short = "t", long = "temperature")] temp: u32, #[structopt(short = "d", long = "duration", default_value = "3600")] dur: u64, // ... }

https://github.com/TeXitoi/structopt

slide-35
SLIDE 35

Procedural Macro Gallery

gobject_gen! { class MyBread: GObject { slices_left: Cell<i32>, consumers: RefCell<Vec<String>>, } impl MyClass { virtual fn eat_slice(&self, who: &str) { // ... } } }

https://gitlab.gnome.org/federico/gnome-class

slide-36
SLIDE 36

Procedural Macro Gallery

#[no_panic] fn bake(at: u32) -> Bread { assert!(at >= 350); // ... }

https://github.com/dtolnay/no-panic

slide-37
SLIDE 37

🍟 Questions? 🥫

  • https://github.com/dtolnay/proc-macro-workshop
  • https://doc.rust-lang.org/reference/procedural-macros.html
  • https://docs.rs/syn
  • https://docs.rs/quote