こちらの記事は Rustマクロ冬期講習アドベントカレンダー 20日目の記事です!
前回は関数風マクロについて軽いハンズオンを行いました。今回は属性風マクロに取り組みます!
属性風マクロの定義・構造
Cargo.toml
に、 [lib] proc-macro = true
という項目を追加した上で、 src/lib.rs
に、
use proc_macro::TokenStream;
#[proc_macro_attribute]
pub fn マクロ名(attr: TokenStream, item: TokenStream) -> TokenStream {
// ...
}
というように #[proc_macro_attribute]
を付けた関数を定義すると、 #[マクロ名(アトリビュート)]
みたいに呼び出せる属性風マクロが定義されます。
三種の神器クレート syn quote proc-macro2 を使って入力の attr: proc_macro::TokenStream
, item: proc_macro::TokenStream
を加工していくことで出力を作ります。
attr
が #[マクロ名(アトリビュート)]
のうちの アトリビュート
にあたる部分で、 item
がこのマクロを付与した対象(アイテム)になります。
なお、 属性風マクロが何も出力しないと、入力されたアイテムはコードから除かれることになります。その分柔軟な書き方が可能というわけです。
属性風マクロ「 dbg_stmts
」
本マクロを付与した関数のブロック内にある各文の実行前に、文の内容をデバッグ出力するマクロを作ってみようと思います1!
#[dbg_stmts]
fn hoge() {
let hoge = 10;
let fuga = hoge + 20;
let bar = vec![hoge, fuga];
println!("{:?}", bar);
}
こちらが以下と同等になるように展開されることを目指します!
fn hoge() {
println!("[dbg_stmt] {}", "let hoge = 10;");
let hoge = 10;
println!("[dbg_stmt] {}", "let fuga = hoge + 20;");
let fuga = hoge + 20;
println!("[dbg_stmt] {}", "let bar = vec![hoge, fuga];");
let bar = vec![hoge, fuga];
println!("[dbg_stmt] {}", r#"println!("{:?}", bar);"#);
println!("{:?}", bar);
}
なお、属性風マクロにオプションを渡せるようにします!
例: #[dbg_stmts(sep = "===")]
オプション | 機能 |
---|---|
sep |
出力行間にセパレータを出力する設定。デフォルトでは出力なし |
fmt |
各行の println! に渡すフォーマットの指定 |
今回想定しているマクロの最終的な使用方法全体像です。
#[dbg_stmts(
sep = "セパレータ",
fmt = "カスタムフォーマット: {}",
)]
fn hoge() {
文1;
文2;
...
文n;
...
}
// ↓ 展開
fn hoge() {
println!("カスタムフォーマット: {}", "文1");
println!("セパレータ");
文1;
println!("セパレータ");
println!("カスタムフォーマット: {}", "文2");
println!("セパレータ");
文2;
println!("セパレータ");
...
println!("カスタムフォーマット: {}", "文n");
println!("セパレータ"); // 文n 開始前のセパレータ
文n; // ここでの標準出力はセパレータで囲まれる
println!("セパレータ"); // 文n 実行後のセパレータ
...
}
手順 1. 骨組みを作る
RTAと同様に準備します!
cargo new --lib dbg_stmts
[package]
name = "dbg_stmts"
version = "0.1.0"
edition = "2021"
+[lib]
+proc-macro = true
[dependencies]
前回同様、 src/lib.rs
はシンプルに保ち実装は src/impls.rs
で行うディレクトリ構造にします。
.
├── Cargo.lock
├── Cargo.toml
├── src
│ ├── impls.rs
│ ├── lib.rs
│ └── main.rs # マクロの動作確認用
└── target
ここまでは前回の関数風マクロとほとんど同じですが、マクロの宣言方法のみ異なります。
mod impls;
use proc_macro::TokenStream;
#[proc_macro_attribute]
pub fn dbg_stmts(attr: TokenStream, item: TokenStream) -> TokenStream {
todo!()
}
関数風マクロは引数一つでしたが、属性風マクロは「属性部分(#[macro(...)]
)」と「アイテム部分(fn hoge() {...}
の部分)」の2つの引数を持ちます!ただし今回アイテム部分は関数のみ受け取ることにします。
続き。三種の神器を cargo add
しておきます。
cargo add proc-macro2 quote
cargo add syn --features full --features extra-traits
手順 2. 入力をパースする
属性部分は追加機能なので後ほど取り組むとして、目的の機能である行間出力を先に実装します!
#[dbg_stmts]
fn hoge() {
文;
文;
...
}
アイテム部分の input
は syn::ItemFn
として受け取れればそれでよいので、前回と異なり入力用の構造体は特に設けないことにします。(後で属性部分をパースするために結局用意しますが...)
let func = parse_macro_input!(input as ItemFn);
一旦関数をそのまま出力するように書いておきます。
mod impls;
use proc_macro::TokenStream;
use quote::ToTokens;
use syn::{parse_macro_input, ItemFn};
#[proc_macro_attribute]
pub fn dbg_stmts(_attr: TokenStream, item: TokenStream) -> TokenStream {
let func = parse_macro_input!(item as ItemFn);
// 行間出力を加える処理をここで行う
func.into_token_stream().into()
}
手順 3. 出力を考える
syn::ItemFn を受け取り、 syn::ItemFn
を出力する、でも良いのですが、 可変参照を受け取り中身を書き換えるほうが( Span
の再設定が要らないなど)色々と楽なので、今回は後者で行こうと思います!
改変対象である syn::ItemFn は次のような定義になっています。
pub struct ItemFn {
pub attrs: Vec<Attribute>,
pub vis: Visibility,
pub sig: Signature,
pub block: Box<Block>,
}
フィールド名 | 型 | 説明 |
---|---|---|
attrs |
Vec< Attribute >
|
本マクロ以外に関数に付与されている属性情報 |
vis |
Visibility |
pub や pub(crate) などの可視性 |
sig |
Signature |
async fn func(arg: T) -> T みたいな関数のシグネチャ部分 |
block |
Box< Block >
|
{} の中身。関数の処理内容。 今回の改変対象
|
そして改変対象の syn::Block
の定義は以下です。
pub struct Block {
pub brace_token: Brace,
pub stmts: Vec<Stmt>,
}
フィールド名 | 型 | 説明 |
---|---|---|
brace_token |
Brace |
{ と } のカッコの部分。主にもとの Span を保存する目的で設けられています(多分)。 |
stmts |
Vec< Stmt >
|
ブロックを構成する文のリストです。 今回の改変対象 |
&mut func.block
で &mut Block
を得て、 stmts
に新しい Vec<Stmt>
を入れ直す形で改変します。書き下す処理のイメージです。
use syn::{parse_quote, Block};
// insert_println_between_stmts(&mut func.block) として呼び出す
pub fn insert_println_between_stmts(block: &mut Block) {
block.stmts = /* Vec<Stmt>書き換え */;
}
では早速いよいよ(?) /* Vec<Stmt>書き換え */
の部分を実装していきます。
block.stmts = block
.stmts
.iter()
// .map系処理で Stmt ごとに新たなパーツを生成
.collect();
map系処理の部分で .map(|stmt| stmtからTokenStream生成)
というように書いても良いのですが、それでは最終的な結果は Vec<Stmt>
ではなく TokenStream
になってしまいます。
そこでクロージャ部分は |stmt| vec![追加のデバッグ出力文, stmt]
となるようにし、二重配列になるので flat_map
を使うことで Vec<Stmt>
でコレクトするようにします。
block.stmts = block
.stmts
.iter()
.flat_map(|stmt| {
vec![
/* 追加のデバッグ出力文 */,
stmt.clone(),
]
})
.collect();
あとは /* 追加のデバッグ出力文 */
に出力したいデバッグ文を書くだけです!
ここは宣言マクロに似た感じでそのままテンプレートを書きたいので、 syn::parse_quote!
マクロを利用します。このマクロの結果は TokenStream
ではなく型推論の上で syn::Stmt
になります。
parse_quote! { println!("[dbg_stmts] {}", stringify!(#stmt)); },
parse_quote!
の中身の println!
では、デバッグ出力したい文部分を stringify!
マクロで囲んでいます。トークン木を渡して出力させるとダブルクォート "
で囲んでそのまま文字列にしてくれるとても便利なマクロです!
ここまでで全体像は次のようになります。
use syn::{parse_quote, Block};
pub fn insert_println_between_stmts(block: &mut Block) {
block.stmts = block
.stmts
.iter()
.flat_map(|stmt| {
vec![
parse_quote! { println!("[dbg_stmts] {}", stringify!(#stmt)); },
stmt.clone(),
]
})
.collect();
}
mod impls;
use proc_macro::TokenStream;
use quote::ToTokens;
use syn::{parse_macro_input, ItemFn};
use impls::insert_println_between_stmts;
#[proc_macro_attribute]
pub fn dbg_stmts(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut func = parse_macro_input!(item as ItemFn);
insert_println_between_stmts(&mut func.block);
func.into_token_stream().into()
}
呼び出し側は次のように呼び出せます。
use dbg_stmts::dbg_stmts;
#[dbg_stmts]
#[allow(unused)]
fn hoge() -> u32 {
let hoge = 10;
let fuga = hoge + 20;
let bar = vec![hoge, fuga];
println!("{:?}", bar);
100
}
fn main() {
let _ = hoge();
}
cargo expand
した結果は次のようになり、
$ cargo expand --bin dbg_stmts
Checking dbg_stmts v0.1.0 (/path/to/dbg_stmts)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
use dbg_stmts::dbg_stmts;
#[allow(unused)]
fn hoge() -> u32 {
{
::std::io::_print(format_args!("[dbg_stmts] {0}\n", "let hoge = 10;"));
};
let hoge = 10;
{
::std::io::_print(format_args!("[dbg_stmts] {0}\n", "let fuga = hoge + 20;"));
};
let fuga = hoge + 20;
{
::std::io::_print(
format_args!("[dbg_stmts] {0}\n", "let bar = vec! [hoge, fuga];"),
);
};
let bar = <[_]>::into_vec(#[rustc_box] ::alloc::boxed::Box::new([hoge, fuga]));
{
::std::io::_print(
format_args!("[dbg_stmts] {0}\n", "println! (\"{:?}\", bar);"),
);
};
{
::std::io::_print(format_args!("{0:?}\n", bar));
};
{
::std::io::_print(format_args!("[dbg_stmts] {0}\n", "100"));
};
100
}
fn main() {
let _ = hoge();
}
出力結果は次のとおりになりました。とりあえず基本機能は完成したようです!
$ cargo run -q
[dbg_stmts] let hoge = 10;
[dbg_stmts] let fuga = hoge + 20;
[dbg_stmts] let bar = vec! [hoge, fuga];
[dbg_stmts] println! ("{:?}", bar);
[10, 30]
[dbg_stmts] 100
手順 4. 属性(アトリビュート)にあるオプションの実装
基本機能ができたので、ここからは sep
や fmt
といった属性オプション 2 を受け取りマクロの出力結果を調整する機能を作りこんでいきます!
#[dbg_stmts(
// ↓ 受け取れるようにする
sep = "セパレータ",
fmt = "カスタムフォーマット: {}",
)]
fn hoge() {
文1;
文2;
...
}
第一引数の attr: TokenStream
で属性オプション部分を受け取れます。 dbg!
マクロを利用してどんなトークン木を受け取れているか確認してみます。
#[proc_macro_attribute]
pub fn dbg_stmts(attr: TokenStream, /* <- 属性部分の入力! */ item: TokenStream) -> TokenStream {
+ dbg!(&attr);
let mut func = parse_macro_input!(item as ItemFn);
insert_println_between_stmts(&mut func.block);
func.into_token_stream().into()
}
[src/lib.rs:11:5] &attr = TokenStream [
Ident {
ident: "sep",
span: #0 bytes(86..89),
},
Punct {
ch: '=',
spacing: Alone,
span: #0 bytes(90..91),
},
Literal {
kind: Str,
symbol: "セパレータ",
suffix: None,
span: #0 bytes(92..109),
},
Punct {
ch: ',',
spacing: Alone,
span: #0 bytes(109..110),
},
Ident {
ident: "fmt",
span: #0 bytes(115..118),
},
Punct {
ch: '=',
spacing: Alone,
span: #0 bytes(119..120),
},
Literal {
kind: Str,
symbol: "カスタムフォーマット: {}",
suffix: None,
span: #0 bytes(121..157),
},
Punct {
ch: ',',
spacing: Alone,
span: #0 bytes(157..158),
},
]
#[dbg_stmts( ... )]
の ...
にあたる部分をそのままトークン木として受け取れています。これをこちらで用意した以下の構造体にパースできるようにします。
#[derive(Debug)]
pub struct DbgStmtsOption {
pub sep: Option<LitStr>,
pub fmt: LitStr,
}
セパレータ sep
は Option
型にし、 Some
の時だけ出力することにします。出力フォーマット fmt
も指定に関してはオプションですが、出力には必ず必要なものなので unwrap_or
を利用して得ることにします。
parse_macro_input!(attr as DbgStmtsOption)
として DbgStmtsOption
を得たいので、 syn::Parse
を DbgStmtsOption
にimplします。
impl Parse for DbgStmtsOption {
fn parse(input: ParseStream) -> Result<Self> {
todo!()
}
}
次の方針で実装していきます。
- 1:
,
で区切られてsep = "..."
とfmt = "..."
が入ってきます。そのためparse_terminated
を利用してMetaNameValue
でイイカンジに受けます - 2: 得た
MetaNameValue
ごとにそのvalue
の"..."
部分を得て、必要なものを保存します - 3:
DbgStmtsOption
構造体にキャプチャしたオプションをまとめて、返します- 3.1:
unwrap_or
を利用しfmt
未指定の時は規定値としてLitStr::new("[dbg_stmt] {}", Span::mixed_site())
を作り返します
- 3.1:
use proc_macro2::Span;
use syn::{
parse::{Parse, ParseStream},
parse_quote, Block, Error, Expr, ExprLit, Lit, LitStr, MetaNameValue, Result, Token,
};
// ...省略...
impl Parse for DbgStmtsOption {
fn parse(input: ParseStream) -> Result<Self> {
// 1
// path1 = "value1", path2 = "value2", ... を vec![path = "value"] のようなイテレータとして扱いたい
let meta_name_value_list = input.parse_terminated(MetaNameValue::parse, Token![,])?;
// 2
let mut sep = None;
let mut fmt = None;
// path = "value" を一つずつ
for MetaNameValue { path, value, .. } in meta_name_value_list {
// 2.1
let lit: LitStr = parse_quote!( #value ); // "value" のパース
// 2.2
match path {
p if p.is_ident("sep") => sep = Some(lit),
p if p.is_ident("fmt") => fmt = Some(lit),
_ => (),
}
}
// 3
Ok(Self {
sep,
// 3.1
fmt: fmt.unwrap_or(LitStr::new("[dbg_stmt] {}", Span::mixed_site())),
})
}
}
insert_println_between_stmts
も大幅に書き換えていきます。
- 1:
DbgStmtsOption
を受け取れるようにします- かっこつけて分解構文を使ってこの時点で
sep
とfmt
に分けています
- かっこつけて分解構文を使ってこの時点で
- 2: 関数の最後が式で終わっている場合最後のセパレータを出力すると式の評価値を返せなくなります。これを防止するため、
.peekable()
メソッドで最後かどうかを確認して最後の文の後にはセパレータを出力しないようにします- 2.1: 伴い、
flat_map
を使用していた箇所でwhile let
を使うようにしています - 2.2, 2.3:
sep
がSome
の時だけセパレータを出力するようにしています - 2.3: 最後でない(
stmts.peek().is_some()
が真の)時だけセパレータを出力するようにしています
- 2.1: 伴い、
- 3:
println!("[dbg_stmts] {}", stringify!(#stmt));
とハードコードしていた箇所について、println!(#fmt, stringify!(#stmt));
としてfmt
を設定するようにしています-
fmt
はLitStr
なのでダブルクォートで囲む必要はありません
-
use proc_macro2::Span;
use syn::{
parse::{Parse, ParseStream},
parse_quote, Block, Error, Expr, ExprLit, Lit, LitStr, MetaNameValue, Result, Token,
};
pub fn insert_println_between_stmts(
block: &mut Block,
// 1
DbgStmtsOption { sep, fmt }: DbgStmtsOption,
) {
// 2
let mut stmts = block.stmts.iter().peekable();
let mut res = Vec::new();
// 2.1
while let Some(stmt) = stmts.next() {
let mut v = Vec::with_capacity(4);
// 3
v.push(parse_quote! { println!(#fmt, stringify!(#stmt)); });
// 2.2 sepオプション指定時だけ設定
if let Some(sep) = &sep {
v.push(parse_quote! { println!(#sep); });
}
v.push(stmt.clone());
// 2.3 sepオプション指定時だけ&末尾以外で設定
if let (true, Some(sep)) = (stmts.peek().is_some(), &sep) {
v.push(parse_quote! { println!(#sep); });
}
res.extend(v);
}
block.stmts = res;
}
// ...省略...
最後に呼び出し側の src/lib.rs
も修正します。
mod impls;
use proc_macro::TokenStream;
use quote::ToTokens;
use syn::{parse_macro_input, ItemFn};
use impls::{insert_println_between_stmts, DbgStmtsOption};
#[proc_macro_attribute]
pub fn dbg_stmts(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut func = parse_macro_input!(item as ItemFn);
let option = parse_macro_input!(attr as DbgStmtsOption);
insert_println_between_stmts(&mut func.block, option);
func.into_token_stream().into()
}
これでオプションを受け取れるようになりました!
use dbg_stmts::dbg_stmts;
#[dbg_stmts(
sep = "セパレータ",
fmt = "カスタムフォーマット: {}",
)]
#[allow(unused)]
fn hoge() -> u32 {
let hoge = 10;
let fuga = hoge + 20;
let bar = vec![hoge, fuga];
println!("{:?}", bar);
100
}
fn main() {
let _ = hoge();
}
cargo expand
の結果は長いので今回は実行時標準出力のみ確認しておきます。
$ cargo run -q
カスタムフォーマット: let hoge = 10;
セパレータ
セパレータ
カスタムフォーマット: let fuga = hoge + 20;
セパレータ
セパレータ
カスタムフォーマット: let bar = vec! [hoge, fuga];
セパレータ
セパレータ
カスタムフォーマット: println! ("{:?}", bar);
セパレータ
[10, 30]
セパレータ
カスタムフォーマット: 100
セパレータ
[10, 30]
の出力や最後のセパレータが1つない等より想定通りであることを確認できました!
発展 正しいエラーハンドリング
Parse
を DbgStmtsOption
に実装する際、 "..."
部分のパースについて記述が煩雑になる関係で 説明が面倒だったので 一旦 parse_quote
を利用して LitStr
に変換していました。
impl Parse for DbgStmtsOption {
fn parse(input: ParseStream) -> Result<Self> {
// ...省略...
// path1 = "value1", path2 = "value2", ...
for MetaNameValue { path, value, .. } in meta_name_value_list {
let lit: LitStr = parse_quote!( #value ); // "value" のパース
// ...省略...
}
// ...省略...
}
}
しかし parse_quote
は変換が失敗しないことを前提にしたマクロで、外からの入力をそのまま渡す目的のものではありません。
parse_quote
でのパニックに限らず、マクロがパニックすると、以下の画像のようにマクロに渡したアイテム全体に赤線が引かれてしまいます。
詳細は Rust手続きマクロ エラーハンドリング手法 にて解説していますが、 Parse
トレイトの parse
メソッドに関してはパニックさせず syn::Result
を返すようにすれば適切なエラーハンドリングができるようになります。
use proc_macro2::Span;
use syn::{
parse::{Parse, ParseStream},
parse_quote, Block, Error, Expr, ExprLit, Lit, LitStr, MetaNameValue, Result, Token,
};
impl Parse for DbgStmtsOption {
fn parse(input: ParseStream) -> Result<Self> {
let meta_name_value_list = input.parse_terminated(MetaNameValue::parse, Token![,])?;
let mut sep = None;
let mut fmt = None;
for MetaNameValue { path, value, .. } in meta_name_value_list {
- let lit: LitStr = parse_quote!( #value );
+ let Expr::Lit(ExprLit {
+ lit: Lit::Str(lit), ..
+ }) = value
+ else {
+ return Err(Error::new_spanned(value, "expected string literal"));
+ };
match path {
p if p.is_ident("sep") => sep = Some(lit),
p if p.is_ident("fmt") => fmt = Some(lit),
_ => (),
}
}
Ok(Self {
sep,
fmt: fmt.unwrap_or(LitStr::new("[dbg_stmt] {}", Span::mixed_site())),
})
}
}
詳細を解説します。 MetaNameValue
で分解した value
は今列挙体 syn::Expr
になっています。ここから内側の値をパターンマッチで取っていき、 LitStr
までマッチさせて変数 lit
に代入させています。
列挙体なので論駁可能であり、そのため let-else
文を利用しています。失敗した時には syn::Error
を返します。
let Expr::Lit(ExprLit {
lit: Lit::Str(lit), ..
}) = value
else {
return Err(Error::new_spanned(value, "expected string literal"));
};
Error::new_spanned
の部分が今回のミソで、マクロがエラーとなった原因箇所を赤下線でハイライトさせるために、エラーの該当箇所の情報( proc_macro::Span
)を持つ value
を第一引数に渡しています。
これで、マクロはパニックせずコンパイルエラー要因となる箇所を正しく提示できるようになります!
まとめ・所感
最終的な全体像は次の通りになります!
mod impls;
use proc_macro::TokenStream;
use quote::ToTokens;
use syn::{parse_macro_input, ItemFn};
use impls::{insert_println_between_stmts, DbgStmtsOption};
#[proc_macro_attribute]
pub fn dbg_stmts(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut func = parse_macro_input!(item as ItemFn);
let option = parse_macro_input!(attr as DbgStmtsOption);
insert_println_between_stmts(&mut func.block, option);
func.into_token_stream().into()
}
use proc_macro2::Span;
use syn::{
parse::{Parse, ParseStream},
parse_quote, Block, Error, Expr, ExprLit, Lit, LitStr, MetaNameValue, Result, Token,
};
pub fn insert_println_between_stmts(
block: &mut Block,
DbgStmtsOption { sep, fmt }: DbgStmtsOption,
) {
let mut stmts = block.stmts.iter().peekable();
let mut res = Vec::new();
while let Some(stmt) = stmts.next() {
let mut v = Vec::with_capacity(4);
v.push(parse_quote! { println!(#fmt, stringify!(#stmt)); });
if let Some(sep) = &sep {
v.push(parse_quote! { println!(#sep); });
}
v.push(stmt.clone());
if let (true, Some(sep)) = (stmts.peek().is_some(), &sep) {
v.push(parse_quote! { println!(#sep); });
}
res.extend(v);
}
block.stmts = res;
}
#[derive(Debug)]
pub struct DbgStmtsOption {
pub sep: Option<LitStr>,
pub fmt: LitStr,
}
impl Parse for DbgStmtsOption {
fn parse(input: ParseStream) -> Result<Self> {
let meta_name_value_list = input.parse_terminated(MetaNameValue::parse, Token![,])?;
let mut sep = None;
let mut fmt = None;
for MetaNameValue { path, value, .. } in meta_name_value_list {
let Expr::Lit(ExprLit {
lit: Lit::Str(lit), ..
}) = value
else {
return Err(Error::new_spanned(value, "expected string literal"));
};
match path {
p if p.is_ident("sep") => sep = Some(lit),
p if p.is_ident("fmt") => fmt = Some(lit),
_ => (),
}
}
Ok(Self {
sep,
fmt: fmt.unwrap_or(LitStr::new("[dbg_stmt] {}", Span::mixed_site())),
})
}
}
use dbg_stmts::dbg_stmts;
#[dbg_stmts(sep = "=========", fmt = "### {} ###")]
#[allow(unused)]
fn hoge() -> u32 {
let hoge = 10;
let fuga = hoge + 20;
let bar = vec![hoge, fuga];
println!("{:?}", bar);
100
}
fn main() {
let _ = hoge();
}
そして cargo expand
の結果は次のようになり(長いので折りたたんでおきました)、
cargo expand
$ cargo expand --bin dbg_stmts
Checking dbg_stmts v0.1.0 (/path/to/dbg_stmts)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
use dbg_stmts::dbg_stmts;
#[allow(unused)]
fn hoge() -> u32 {
{
::std::io::_print(format_args!("### {0} ###\n", "let hoge = 10;"));
};
{
::std::io::_print(format_args!("=========\n"));
};
let hoge = 10;
{
::std::io::_print(format_args!("=========\n"));
};
{
::std::io::_print(format_args!("### {0} ###\n", "let fuga = hoge + 20;"));
};
{
::std::io::_print(format_args!("=========\n"));
};
let fuga = hoge + 20;
{
::std::io::_print(format_args!("=========\n"));
};
{
::std::io::_print(format_args!("### {0} ###\n", "let bar = vec! [hoge, fuga];"));
};
{
::std::io::_print(format_args!("=========\n"));
};
let bar = <[_]>::into_vec(#[rustc_box] ::alloc::boxed::Box::new([hoge, fuga]));
{
::std::io::_print(format_args!("=========\n"));
};
{
::std::io::_print(format_args!("### {0} ###\n", "println! (\"{:?}\", bar);"));
};
{
::std::io::_print(format_args!("=========\n"));
};
{
::std::io::_print(format_args!("{0:?}\n", bar));
};
{
::std::io::_print(format_args!("=========\n"));
};
{
::std::io::_print(format_args!("### {0} ###\n", "100"));
};
{
::std::io::_print(format_args!("=========\n"));
};
100
}
fn main() {
let _ = hoge();
}
実行結果では次のとおり次に実行される文を確認できるようになりました!
$ cargo run -q
### let hoge = 10; ###
=========
=========
### let fuga = hoge + 20; ###
=========
=========
### let bar = vec! [hoge, fuga]; ###
=========
=========
### println! ("{:?}", bar); ###
=========
[10, 30]
=========
### 100 ###
=========
関数マクロと同様に、
-
syn
クレートを利用して入力をパース - 入力となる関数
func
のフィールドを書き換えることで結果を生成する-
syn::parse_quote!
マクロを利用して挿入したい文を生成する
-
- 属性オプション部分も同じようにパースして利用する
-
syn::Error
を使った正しいエラーハンドリングをする
という手順を解説し、文の間にデバッグ出力を行う属性風マクロを作りました!
アイテムにしか付けられないという制約はありますが、今回見せたような、受け取ったアイテムの可読性を維持しつつちょっとした記述を追加するという場合に、属性風マクロは重宝しますね!
次回は属性風マクロと似ているのだけども雰囲気が違うderiveマクロに取り組みます!