自己紹介

出田 守と申します。
しがない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のガードで値が移動する場合でも共有参照が取れるようになった

 

    関数やクロージャ、関数ポインタのパラメータに属性を付けられるようになった

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

    • https://blog.rust-lang.org/2019/11/07/Rust-1.39.0.html

 

    • https://github.com/rust-lang/rust/blob/stable/RELEASES.md#compatibility-notes

 

    • https://tech-blog.optim.co.jp/entry/2019/11/08/080000

 

    https://tech-blog.optim.co.jp/entry/2019/11/08/163000

マクロ

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

cargo run –verboseでrustcのコマンドを確認。
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]を付ける

今回はここまで。

bannerAds