LoginSignup
6
2

More than 3 years have passed since last update.

Rust勉強中 - その26 -> マクロ

Posted at

自己紹介

出田 守と申します。
しがないPythonプログラマです。
情報セキュリティに興味があり現在勉強中です。CTFやバグバウンティなどで腕を磨いています。主に低レイヤの技術が好きで、そっちばかり目が行きがちです。

Rustを勉強していくうえで、読んで学び、手を動かし、記録し、楽しく学んでいけたらと思います。

環境

新しい言語を学ぶということで、普段使わないWindowsとVimという新しい開発環境で行っています。
OS: Windows10 Home 64bit 1903
CPU: Intel Core i5-3470 @ 3.20GHz
Rust: 1.39.0
RAM: 8.00GB
Editor: Vim 8.1.1
Terminal: PowerShell

前回

前回は並列処理について学びました。
Rust勉強中 - その25

まず1.39.0にアップデート

11/7-8にRust 1.39.0がリリースされたようです。

本題に入る前にアップデートしておきます。

$ rustup update stable
$ cargo --version
cargo 1.39.0 (1c6ec66d5 2019-09-30)
$ rustc --version
rustc 1.39.0 (4560ea788 2019-11-04)
$ rustdoc --version
rustdoc 1.39.0 (4560ea788 2019-11-04)

主な更新内容

  • async/.awaitが安定化
  • matchのガードで値が移動する場合でも共有参照が取れるようになった
  • 関数やクロージャ、関数ポインタのパラメータに属性を付けられるようになった

などなど
詳しくは以下を参照:

マクロ

Rustではマクロがサポートされています。関数で表現できない処理や、短縮したい処理などをマクロとして定義して呼び出します。

定義

マクロの定義にはmacro_rules!を使います。

macro_rules! macro_name {
    ...
}

このほかに、組込みマクロ手続きマクロがあります。
この記事では、macro_rules!を使ったマクロのみに焦点を絞ります。

マクロの呼び出しは以下のように!を付けて呼び出します。

macro_name!(...);

パターンマッチ

マクロは呼び出し時に与えられた引数に対して、上から順番にパターンマッチを行います。マッチすれば定義された処理を実行します。

macro_rules! macro_name {
    (pattern1) => { // rule1
        ...
    };
    (pattern2) => { // rule2
        ...
    };
    ...
}

ちなみに、パターンや処理の括弧は、適切に対応していれば(){}[]でも良いそうです。
さらに、マクロ呼び出し時も括弧が対応していれば(){}[]でも良いです。この時{}のみセミコロンは不要です。

展開

マクロの展開はコンパイルの早い段階で行われます。そして何らかのRustコードに変換されます。
vec!より良い例が浮かばなかったので、vec!を例に説明します。

macro_rules! vec {
    ($elem:expr ; $n:expr) => { // rule1
        ::std::vec::from_elem($elem, $n)
    };
    ($($x:expr),*) => { // rule2
        <[_]>::into_vec(Box::new([$($x),*]))
    };
    ($($x:expr),+,) => { // rule3
        vec![$($x),*]
    };
}

\$elemや\$n, \$xはマッチした値を代入する変数のようなものです。パターン中の:exprはフラグメント指定子やフラグメント型と呼ばれてるみたいです(ここでは単にフラグメントと呼びます)。フラグメントは、マッチする値の種類を決めます。exprの場合はRustの式を意味します。フラグメントには以下があります。

フラグメント マッチ対象 直後のトークン
expr ,;
stmt ,;
ty ,;=
path パス ,;=
pat パターン
item アイテム
block ブロック
meta 属性のボディ部
ident 識別子
tt トークンツリー

フラグメントには、直後に来るトークンの制約を受けるものがあります。例えば、exprの場合は、直後には,;しか来ません。
identとttに関してはRustの構文に属さない値にマッチさせることができます。
identは任意の識別子に、ttは適切に対応した括弧に囲まれている部分あるいは、括弧に囲まれていない単一のトークンにマッチします。

vec!は以下の3パターンで呼び出すことができます。

vec![0; 3];     // rule1
vec![0, 1, 2];  // rule2
vec![0, 1, 2,]; // rule3

まず、vec![0; 3];の引数は0; 3となり、rule1に当てはめます。rule1のパターンは$elem:expr ; $n:exprです。まず0は$elem:exprにマッチします。次に;はそのまま;にマッチします。最後に3$n:exprにマッチします。結果、rule1にマッチすることになり、その処理を実行します。
パターンマッチ時はコメントやホワイトスペースは無視されます。

繰り返し

次に、vec![0, 1, 2]のパターンマッチをみていきます。この場合引数は0, 1, 2になります。最初にrule1に当てはめますがマッチしません。次にrule2に当てはめてみます。rule2のパターンは$($x:expr),*のようになっています。これの意味は、「$x:exprのカンマ区切りで0回以上の繰り返し」という意味です。このとき、繰り返しの対象は$($$x:expr)となります。正規表現のようにカンマの繰り返し出ないことに注意です。繰り返しの表現は*+があります。

パターン 意味
$(...)* 0個以上にマッチ
$(...)+ 1個以上にマッチ

rule2のパターンマッチに戻ります。0, 1, 2はカンマをセパレータに繰り返されているので、マッチします。結果、rule2の処理を実行します。ちなみに、処理中でも繰り返しが使われています。

再帰

最後にvec![0, 1, 2,]のパターンマッチをみていきます。引数は0, 1, 2,となり、最後にカンマがきた形になっています。これは、rule1にマッチしません。次のrule2にもマッチしません。最後にカンマがこないからです。
最後のrule3に当てはめてみます。rule3のパターンは$($x:expr),+,です。意味は「$x:exprのカンマ区切りで1回以上の繰り返しで最後にカンマ」ですね。これは、引数とマッチしますので、rule3の処理が実行されます。
rule3の処理はvec![$($x),*]となっており最後のカンマを抜いて、自分自身を呼び出しています。つまり、マクロを再帰的に呼び出していることになります。

デバッグ

マクロはコンパイル時にRustコードに展開されます。展開後のコードを確認するには、以下の手順で行います。

  1. Rustをnightlyにします。rustup default nightly
  2. cargo run --verboseでrustcのコマンドを確認。
  3. rustcコマンドのオプションに-Z unstable-options --pretty expandedを追加して実行

vec!の展開結果は以下のようになりました。分かりやすいように改行やインデントを入れています。

macro_rules! vec {
    ($ elem : expr ; $ n : expr) =>
        { :: std :: vec :: from_elem ($ elem, $ n) } ; 
    ($ ($ x : expr), *) =>
        { < [_] > :: into_vec (Box :: new ([$ ($ x), *])) } ; 
    ($ ($ x : expr), +,) => { vec ! [$ ($ x), *] } ;
}

また、log_syntax!を使う方法や、trace_macros!(true)を使う方法もあります。

インポートとエクスポート

単一クレートでは、

  • あるモジュールで見えているマクロは、自動的にその子モジュールからもそのマクロが見える
  • あるモジュールから親モジュールへエクスポートするには#[macro_use]を使う

複数クレートでは、

  • ほかのクレートからマクロをインポートするには、extern crate宣言に#[macro_use]を付ける
  • クレート内のマクロをエクスポートするには、マクロそれぞれに#[macro_export]を付ける

今回はここまで。

6
2
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
6
2