Derive マクロのメモです。
リソース
文書
- (Google で検索)
- Macros - The Rust Programming Language
- Procedual Macros - The Rust References
- Rust の procedural macro を操って黒魔術師になろう〜proc-macro-workshop の紹介 | Zenn
コード
人のコードを読むのが derive macro 習得の近道だと思います。
頑張ってモチベーションが上がるコードを探します。
主要クレート
必須:
おすすめ:
メタ的逆引き
Q. マクロの展開結果を見たい
A. cargo-expand を使います。
$ cargo install cargo-expand
~~
$ cargo expand --tests test-a
~~
ただし derive マクロの出力が構文エラーの場合は、展開結果を 確認できません 。その間は trace_macros
を使います:
//! `lib.rs` で `trace_macros` を有効化
#![feature(trace_macros)]
// このファイルのマクロ展開結果を見る
trace_macros!(true);
pub mod inspect;
trace_macros!(false);
pub mod visit;
Q. Derive マクロのテストがしたい
以下のような構成を想定します:
.
├── my-crate
└── my-crate-derive
my-crate-derive
の中で #[derive(MyMacro)]
を使った unit test は書けません:
#[cfg(test)]
mod test {
// できない!
#[derive(MyMacro)]
pub MyStruct;
#[test]
fn test_my_struct() { /* ~~ */ }
}
[dev-dependencies]
# `src/` 以下で `my-crate` に触れると循環参照になってしまう
my-crate = { path = "../my-crate" }
A. Integration test を使います。
.
├── my-crate
└── my-crate-derive
├── src
└── tests
└── my_test.rs
[dev-dependencies]
# `tests/` 以下では循環参照にならない
my-crate = { path = "../my-crate" }
tests/my_test.rs
は独立したクレートとして扱われるため、循環参照が起こりません。
インテグレーションテストは
tests/it
に集約するのが定石のようです。 (Delete Cargo Integration Tests)
Q. 外部クレートのアイテムに #[derive(MyMacro)]
を追加したい
A. fork しない限り不可能です。
macro_rules!
などを駆使して外部から地道に MyTrait
を実装することになります。 serde
の impls.rs
などが参考になりそうです。
また orphan rules に反するトレイト実装は作れないことを留意します。
実装向け逆引き
Q. 構造体/列挙体の分類分けは?
A. いずれもフィールドの種類で分離できます。フィールドは 3 種類です:
pub struct UnitStruct;
pub struct TupleStruct(f32);
pub struct NamedFieldsStruct {
a: f32,
}
pub enum ComplexEnum {
UnitVariant,
TupleVariant(f32),
NamedFieldsVariant { a: f32 },
}
フィールドを処理する部分は struct/enum で共通化できるはずです。
Q. 全フィールドが MyTrait
を実装する場合のみ MyTrait
を実装したい
A. where
clause を拡張し、以下のような impl
ブロックを出力します:
pub struct MyStruct<T> {
f32: T,
items: Vec<T>
}
impl<T> MyTrait forMyStruct<T>
where
f32: MyTrait,
T: Vec<T>: MyTrait,
{
// ~~
}
Q. #[attribute(meta = literal)]
をパースしたい
syn
は custom attribute を Vec<Attribute>
までしかパースしてくれません。 特定の型 ユーザ指定の型 に落とし込むには (たぶん) 手動でパースする必要があります。
A. darling を使用します。使用例:
/// `darling` がフィールドをこの型にパースしてくれる:
#[derive(FromField, Clone)]
#[darling(attributes(visit))]
pub struct FieldArgs {
pub ident: Option<Ident>,
// pub vis: Visibility,
pub ty: Type,
// pub attrs: Vec<Attribute>,
// 以下、属性から自動的にパースされるもの
/// `#[visit(skip = true|false)]`
#[darling(default)]
pub skip: bool,
/// `#[visit(rename = "..")]`
#[darling(default)]
pub rename: Option<String>,
}
darling
ユーザは少ないかもしれません。皆さんは自力でVec<Attribute>
をパースされている……?