本記事で伝えたいこと
- 宣言マクロは基本的に
macro_rules!
で定義された後に利用可能になる -
#[macro_use]
アトリビュート- モジュールや
extern crate xxx
につけることでモジュール内・クレート内のマクロを この行以降 使用可能にする
- モジュールや
-
#[macro_export]
アトリビュート- 宣言マクロにつけることでマクロが クレートルートに 公開される。マクロやモジュールの可視性(pub等)は無視される。
- クレート外部に宣言マクロを公開する手段は今のところこのアトリビュートのみなので、 公開したいマクロは最初からクレートルートに設置したほうがよい
こちらの記事は Rustマクロ冬期講習アドベントカレンダー 13日目の記事です!
今回は宣言マクロのスコープ(有効範囲)やインポート・エクスポートの話をしたいと思います。以下、今回の出典元です。
出典元のLittle Bookの説明がなかなか難しく、本記事は嘘を付いている可能性が高いのでLittle Bookの方も適宜参照してほしいです。また明確な誤りがあればコメントくださると幸いです。
スコープについて
宣言マクロのスコープ、謎挙動が多いのでそのまとめです。
macro_rules
で直接ファイルに記述した宣言マクロは「マクロだ」と思うと割と自然なのですが関数等と同一視し始めると謎な挙動が結構あります。
- (1): マクロ定義 後 (定義した行以降)でのみマクロにアクセスできる
- (2): サブモジュール内でも有効(ただしこちらもマクロ定義 後 のみ)
- (3): 上書き可能
mod a {
// X!(); // undefined
}
mod b {
// X!(); // undefined
macro_rules! X { () => {}; } // (1)
X!(); // defined
mod bb {
X!(); // defined (2)
macro_rules! X { () => { fn hoge() {} }; } // override (3)
X!(); // defined
}
}
mod c {
// X!(); // undefined
}
Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3e73eee485d94023a70cc5b453cde718
「スクリプト言語の関数の定義順の感覚やC言語のプロトタイプ宣言的な感覚はRustには不要と思っていたけどマクロで出てきたんだ〜」みたいな感覚になりますね
マクロのインポート: #[macro_use]
アトリビュート
モジュール・クレート下にあるマクロを この行以降で 利用可能にするアトリビュートです。モジュールやクレートの上に付与します。
先程の例で X
マクロは mod c {}
では利用不可能でしたが、 #[macro_use]
を付与することで利用可能になります!
mod a {
// X!(); // 相変わらずここからは使えない
}
// X!(); // ここでも使えない
#[macro_use] // mod bで利用可能なマクロをこの行以降で利用可能にする
mod b {
macro_rules! X { () => {}; }
}
X!(); // defined
mod c {
X!(); // defined
}
Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b27c19aaec61f488f5ec0fe0a0d223ed
他のクレートにあるマクロは、 extern crate xxx
の上に #[macro_use]
を付与することで利用可能にします。
#[macro_export]
macro_rules! hoge {
() => {};
}
#[macro_use]
extern crate crate_a;
hoge!();
fn main() {}
#[macro_use]
と書くと対象クレート以下の全てのマクロを利用可能になってしまいます。yyy
マクロだけ利用したい、という場合は #[macro_use(yyy)]
という記法が使えます!
ただしこの記法は extern crate xxx
に対してしか使えず mod xxx {}
には使えないみたいです。なんでや!
#[macro_export]
macro_rules! hoge {
() => {};
}
#[macro_export]
macro_rules! fuga {
() => {};
}
#[macro_use(hoge)]
extern crate crate_a;
hoge!();
// fuga!(); // <- エラー!
fn main() {}
Edition 2018以降について
Edition 2018以降、 #[macro_export]
によるクレートルートでの宣言マクロ公開は相変わらず必要ですが、#[macro_use]
を extern crate xxx;
上に付ける必要がなくなりました。よって、インポートする側は次のように書けるようになりました!
crate_a::hoge!();
fn main() {}
ただし自クレート内のマクロについてはEdition 2015と特に変更がないため、相変わらず #[macro_use]
を利用する必要があります。
#[macro_use] extern crate xxx;
が要らなくなっただけという感じです。
マクロのエクスポート #[macro_export]
マクロを 全ての可視性(pub等)を無視して, クレートルートで定義したことにする アトリビュートです。マクロの上に付与します。
mod hoge {
mod fuga {
// クレートルートにsecret_macro公開
#[macro_export]
macro_rules! secret_macro {
() => {};
}
// hogeからは見えるがそこより外では見えない
pub fn secret_func() {}
}
fn hoge_func() {
fuga::secret_func();
}
}
pub fn func() {
// fugaはここからは見えない
// hoge::fuga::secret_func();
}
// 可視性を無視して利用可能!
secret_macro!();
#[macro_use]
との違いがわかりにくいかもしれないです。 #[macro_export]
は以下の場合に利用すると良さそうです。
-
他クレートからも利用可能にしたい時
-
#[macro_use]
では不可能です
-
- 自クレート全体で利用したい時
-
#[macro_use]
の場合でも、これを利用してクレートルートに持っていけば可能です - しかし、マクロを利用したいモジュール等を全て
#[macro_use]
行以降に記述しなければならず、面倒です
-
まとめ・所感
色々面倒な仕様があるように見えますが、Edition 2018基準で必要事項をまとめると以下です。
- 基本的には
macro_rules!
で定義した行以降で同一モジュール・サブモジュールから使える - 上の階層でも使いたい場合、
#[macro_use]
を使うことでモジュール内のマクロを一つ上の階層に公開できる - 他のクレートや自クレート全体で使いたい場合、
#[macro_export]
を使うと良い- この時、最初からクレートルートで定義すると吉
#[macro_use]
はなるべく避け、局所的に使うマクロか全体で使うマクロを定義するように心がけるとよさげかもしれません。