Slide 1

Slide 1 text

Procedural macros入門 cipepser 2020/6/16 下町.rs #2

Slide 2

Slide 2 text

自己紹介 ● cipepser(さいぺ) ● リードエンジニア@LayerX ○ R&Dチーム ● 経歴 ○ SIerで証券系システムのネットワーク設計、構築 ○ Fintech会社で新規事業の立ち上げ ● 趣味プロジェクト ○ コンパイラ ○ 正規表現ジェネレータ ○ protocol buffersデコーダ ○ (多くがやりかけ...)

Slide 3

Slide 3 text

Anonifyをリリースしました ソースコード ホワイトペーパー https://github.com/LayerXcom/anonify https://layerx.co.jp/wp-content/uploads/2020/06/anonify.pdf ドキュメント https://layerxcom.github.io/anonify-book/ スライド https://speakerdeck.com/layerx/anonify TEE内のテストにProcedural macrosを利用している

Slide 4

Slide 4 text

アジェンダ 1. Procedural macros概要 2. 通常のRustマクロ 3. Procedural macrosでよく利用される3つマクロたち 4. Custom Attributeの実装

Slide 5

Slide 5 text

Procedural macros概要

Slide 6

Slide 6 text

Procedural macrosをざっくりと理解する ● Rust edition 2018から使えるようになった ● 通常のRustマクロ ○ macro_rules! ○ パターンマッチ ● Procedural macros ○ Function-like macros ■ custom!(...) ○ Derive macros ■ #[derive(CustomDerive)] ○ Attribute macros ■ #[CustomAttribute]

Slide 7

Slide 7 text

通常のRustマクロ

Slide 8

Slide 8 text

macro_rules! vec { () => ( $crate::vec::Vec::new() ); ($elem:expr; $n:expr) => ( $crate::vec::from_elem($elem, $n) ); ($($x:expr),+ $(,)?) => ( <[_]>::into_vec(box [$($x),+]) ); } Proceduralでない通常のRustマクロはパターンマッチがベース https://github.com/rust-lang/rust/blob/master/src/liballoc/macros.rs ● (pattern) => (template); ● *, +による繰り返し ○ 参照は$で対になっている必要がある ● exprやtyなどのフラグメント型 vec![]: vec![“a”; 3]: vec![1,2,3]:

Slide 9

Slide 9 text

Procedural macrosでよく利用される 3つマクロたち

Slide 10

Slide 10 text

proc_macro ● Procedural macrosの実装をサポートするラ イブラリ ● Cargo.tomlのlibで有効化して使う ● proc_macro2というwrapperもある ○ build.rsやmain.rsのような別のコンテキストに proc_macroの機能を持ち込みたいときに使う ● TokenStreamを操作する ○ ASTを直接操作するわけではない ○ Vecをcloneしやすくしたもの ○ TokenTree = lexical token https://doc.rust-lang.org/proc_macro/

Slide 11

Slide 11 text

syn ● 文字列を入力に構文解析 ● validなRustコードであることを保証 ● Structsでもろもろ定義されている ○ 次ページでItemFnを例にみてみます https://docs.rs/syn/1.0.31/syn/

Slide 12

Slide 12 text

例)synで関数名を取得する pub struct ItemFn { pub attrs: Vec, pub vis: Visibility, pub sig: Signature, pub block: Box, } pub struct Signature { pub constness: Option, pub asyncness: Option, pub unsafety: Option, pub abi: Option, pub fn_token: Token![fn], pub ident: Ident, pub generics: Generics, pub paren_token: token::Paren, pub inputs: Punctuated, pub variadic: Option, pub output: ReturnType, } 関数名を持つ

Slide 13

Slide 13 text

quote ● synのデータ構造を受け取り、トークン列に 変換し直す ● #varで補完 ○ 例)取得した関数名を利用したいとき https://docs.rs/quote/1.0.7/quote/ let f = parse_macro_input!(input as ItemFn); let f_ident = &f.sig.ident; let q = quote!( #f_ident .. };

Slide 14

Slide 14 text

Custom Attributeの実装

Slide 15

Slide 15 text

Attributeの復習 #[test] fn check() { let x = 1; assert_eq!(x, 1); } #[test]を付与することで、check()に 「cargo test時に実行される」という意味を付与

Slide 16

Slide 16 text

やること:Custom Attributeで、cargo runでテストを走らせます use utils::{test_case, run_inventory_tests}; #[test_case] fn check() { let x = 1; assert_eq!(x, 1); } #[test_case] fn fail() { let x = 1; assert_eq!(x, 2); } fn main() { run_inventory_tests!(); } ● #[test_case] attributeを自作 ● cargo runで実行 ○ ✖ cargo test

Slide 17

Slide 17 text

ディレクトリ構造とCargo.toml [lib] proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" syn = "1.0" main関数を実装 標準出力の調整など Procedural macrosを実装 proc-macroを有効化

Slide 18

Slide 18 text

#[proc_macro_attribute] pub fn test_case(_attr: TokenStream, input: TokenStream) -> TokenStream { let f = parse_macro_input!(input as ItemFn); let f_ident = &f.sig.ident; let q = quote!( #f inventory::submit!( utils::TestCase( concat!(module_path!(), "::", stringify!(#f_ident)).to_string(), #f_ident ) ); ); q.into() } Attributeを実装する TokenStreamをItemFnとしてparse 失敗したらコンパイルエラー attributeの名前を関数名にする 関数(check()やfail())が展開される

Slide 19

Slide 19 text

pub struct TestCase(pub String, pub fn() -> ()); inventory::collect!(TestCase); #[macro_export] macro_rules! run_inventory_tests { () => { utils::test_start(); let mut ntestcases: u64 = 0u64; let mut failurecases: Vec = Vec::new(); for t in inventory::iter::.into_iter() { utils::test(&mut ntestcases, &mut failurecases, t.1, &t.0); } utils::test_end(ntestcases, failurecases) }; } #[test_case] attrを付与した関数を集める submit!した関数たちを集めてくる utils::test内でt.0を実行する

Slide 20

Slide 20 text

custom attributesを使ってみる use utils::{test_case, run_inventory_tests}; #[test_case] fn check() { let x = 1; assert_eq!(x, 1); } #[test_case] fn fail() { let x = 1; assert_eq!(x, 2); } fn main() { run_inventory_tests!(); } attrを設定 attrを設定 main関数内でテストを実行

Slide 21

Slide 21 text

`cargo test`ではなく、`cargo run`でテストを実行する 関数名(checkやfail)も出力できた

Slide 22

Slide 22 text

● Procedural macrosと通常のマクロを紹介 ● Procedural macrosでよく利用される3つマクロたち ○ proc_macroで、Procedural macrosが有効になる ○ synで、文字列からRustコードに構文解析する ○ quoteで、synのデータ構造からRustコードに変換し直す ● Custom Attributeを実装した ○ 自分でも動かしたい!という方は以下にソースコードを公開してます。 ○ https://github.com/cipepser/rust-custom-attribute まとめ

Slide 23

Slide 23 text

References ● proc_macro - Rust https://doc.rust-lang.org/proc_macro/ ● O'Reilly Japan - プログラミングRust https://www.oreilly.co.jp/books/9784873118550/ ● Procedural Macros - The Rust Reference https://doc.rust-lang.org/reference/procedural-macros.html ● D - マクロ - The Rust Programming Language https://doc.rust-jp.rs/book/second-edition/appendix-04-macros.html ● proc_macro2 - Rust https://docs.rs/proc-macro2/1.0.18/proc_macro2/ ● syn - Rust https://docs.rs/syn/1.0.31/syn/ ● quote - Rust https://docs.rs/quote/1.0.7/quote/ ● inventory - Rust https://docs.rs/inventory/0.1.6/inventory/ ● Procedural Macros に入門していたずらしてみた - Don't Repeat Yourself https://yuk1tyd.hatenablog.com/entry/2018/12/25/192041 ● Procedural macros 雑入門 - slideship.com https://slideship.com/users/@statiolake/presentations/2019/07/CZ9wX4Zi8R93MhxN7jRBK5/?p=2

Slide 24

Slide 24 text

ブロックチェーン技術をもとに、「新たな経済基盤」をつくりだす。 それは、信用や評価のあり方を変え、 業務や生産をはじめとした経済活動の摩擦を解消し、 この国の課題である生産性向上を実現する。 私たちは、そう信じて行動し続けます。 ブロックチェーンが実装された社会、 そこには、これまでの延⻑にはないまったく新しい可能性が広がっている。 LayerXは、デジタル社会への発展を後押しすることで、 経済史に新たな1ページを刻んでいきます。