自己紹介
出田 守と申します。
しがない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コードに展開されます。展開後のコードを確認するには、以下の手順で行います。
- 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]
を付ける
今回はここまで。