0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rustマクロ冬期講習Advent Calendar 2024

Day 9

Rust 宣言マクロ小ネタ集【光編① マクロを作る時に便利なマクロ】

Posted at

こちらの記事は Rustマクロ冬期講習アドベントカレンダー 9日目の記事です!

今回から数回にわたり、重要ではないけど知っておくとちょっとお得かもしれない宣言マクロの小ネタを軽く紹介したいと思います。

以下、今回の出典元です。

Debugging - The Little Book of Rust Macros

超有能文字列化マクロ stringify

ここまでの記事で何度か登場していますが、与えられたトークン木をそのまま "" で囲まれた文字列にしてくれる stringify というマクロがあります。

stringify in std - Rust

Rust
macro_rules! impl_hello {
    ($name:ty) => {
        impl $name {
            fn hello(&self) {
                println!("Hello. My name is {}!", stringify!($name));
            }
        }
    };
}

fn main() {
    struct Hoge;

    impl_hello(Hoge);

    Hoge.hello();
}

Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=9a3768aa8341aea9143d9da298d91427

...便利ですね!特にこれ以上解説することはないのですが、宣言マクロだけではなく手続きマクロでもかなりの頻度で使うマクロなのでここで紹介させていただきました。特に例えば上記で示したような、構造体名などの ident をプログラムで出力したいといった時に頻出です。

マッチした中身を確かめたい

生成されるマクロをデバッグしたい祭、 cargo expand による方法があることを紹介しました。

Rustマクロの事前知識④ 「聴診器 cargo-expand」 #Rust - Qiita

それ以外にも2つ、コンパイル時にマッチした中身を確認するマクロがあるので、ここで紹介したいと思います。

まぁどちらも nightly (実験機能) なのですが :upside_down:

trace_macros

マクロが「どのように」展開されていくかを教えてくれるマクロです。

真偽値を渡して有効化する仕組みになっており、 trace_macros!(true); から trace_macros!(false); で囲まれた範囲の中にあるマクロが展開されていく様子をコンパイル時にログ出力してくれます!

Rust
#![feature(trace_macros)]

macro_rules! each_tt {
    () => {};
    ($_tt:tt $($rest:tt)*) => {each_tt!($($rest)*);};
}

fn main() {
    each_tt!(foo bar baz quux);

    trace_macros!(true); // デバッグON!
    each_tt!(spim wak plee whum);
    trace_macros!(false); // デバッグOFF!

    each_tt!(trom qlip winp xod);
}
ビルド結果
$ cargo +nightly build
   Compiling project v0.1.0 (/path/to/project)
note: trace_macro
  --> src/main.rs:12:5
   |
12 |     each_tt!(spim wak plee whum);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: expanding `each_tt! { spim wak plee whum }`
   = note: to `each_tt! (wak plee whum);`
   = note: expanding `each_tt! { wak plee whum }`
   = note: to `each_tt! (plee whum);`
   = note: expanding `each_tt! { plee whum }`
   = note: to `each_tt! (whum);`
   = note: expanding `each_tt! { whum }`
   = note: to `each_tt! ();`
   = note: expanding `each_tt! {  }`
   = note: to ``

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.49s

Playground: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=ef2a3033512b33bda9489ab15582c1cf

マクロの再帰的な展開もトレースしてくれるので、前回見せたような再帰を利用したマクロがどう展開されていくかをデバッグするのに便利です!

Rust
#![feature(trace_macros)]

macro_rules! list {
    ( $v:expr ) => {
        Rc::new(Cons($v, Rc::new( Nil )))
    };
    ( $v:expr, $($rest:expr),* ) => {
        Rc::new(Cons($v, list!($($rest),*) ))
    };
}

#[allow(dead_code)]
#[derive(Debug)]
enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use std::rc::Rc;
use List::{Cons, Nil};

fn main() {
    trace_macros!(true);
    let _ = list![1, 2, 3, 4, 5];
    trace_macros!(false);
}
展開結果
$ cargo +nightly build
   Compiling project v0.1.0 (/path/to/project)
note: trace_macro
  --> src/main.rs:24:13
   |
24 |     let _ = list![1, 2, 3, 4, 5];
   |             ^^^^^^^^^^^^^^^^^^^^
   |
   = note: expanding `list! { 1, 2, 3, 4, 5 }`
   = note: to `Rc :: new(Cons(1, list! (2, 3, 4, 5)))`
   = note: expanding `list! { 2, 3, 4, 5 }`
   = note: to `Rc :: new(Cons(2, list! (3, 4, 5)))`
   = note: expanding `list! { 3, 4, 5 }`
   = note: to `Rc :: new(Cons(3, list! (4, 5)))`
   = note: expanding `list! { 4, 5 }`
   = note: to `Rc :: new(Cons(4, list! (5)))`
   = note: expanding `list! { 5 }`
   = note: to `Rc :: new(Cons(5, Rc :: new(Nil)))`

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s

Playground: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=e8f0f28bb12f3995a9613c8f4d1b9783

log_syntax

log_syntax in std - Rust

受け取ったメタ変数(というかトークン木...?)をコンパイル時にそのままログ出力してくれるマクロです!

Rust
#![feature(log_syntax)]

macro_rules! list {
    ( $v:expr ) => {
        {
            log_syntax!($v);
            Rc::new(Cons($v, Rc::new( Nil )))
        }
    };
    ( $v:expr, $($rest:expr),* ) => {
        {
            log_syntax!($v);
            Rc::new(Cons($v, list!($($rest),*) ))
        }
    };
}

#[allow(dead_code)]
#[derive(Debug)]
enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use std::rc::Rc;
use List::{Cons, Nil};

fn main() {
    let _ = list![1, 2, 3, 4, 5];
}
展開結果
$ cargo +nightly build
   Compiling project v0.1.0 (/path/to/project)
1
2
3
4
5
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s

Playground: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=da9cbfe2fcd0390e9e70ee23d29b1584

正直使い所わからんかも...局所的に確認したい時は trace_macros より便利かもしれないですね

まとめ

マクロを書く時に便利そうなマクロを紹介しました!

cargo expand と併用して使用するとマクロが幾分か楽に書けるようになるかもしれません、多分...!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?