Derive マクロ入門 (メモ／tips)

Derive マクロのメモです。

# リソース

## 文書

* ([Google で検索](https://www.google.com/search?q=rust%20procedual%20macro))
* [Macros - The Rust Programming Language](https://doc.rust-lang.org/book/ch19-06-macros.html#procedural-macros-for-generating-code-from-attributes)
* [Procedual Macros - The Rust References](https://doc.rust-lang.org/reference/procedural-macros.html)
* [Rust の procedural macro を操って黒魔術師になろう〜proc-macro-workshop の紹介 | Zenn](https://zenn.dev/magurotuna/articles/bab4db5999ebfa)

## コード

人のコードを読むのが derive macro 習得の近道だと思います。
頑張ってモチベーションが上がるコードを探します。

> 一応: [手前味噌1](https://github.com/mrDIMAS/rg3d/tree/master/rg3d-core-derive), [手前味噌2](https://github.com/toyboot4e/rokol/tree/master/rokol_derive), [手前味噌3](https://github.com/not-fl3/miniquad/pull/203/commits)

## 主要クレート

 必須:

* [proc_macro2](https://docs.rs/proc-macro2/latest/proc_macro2/)
* [syn](https://docs.rs/syn/latest/syn/)
* [quote](https://docs.rs/quote/latest/quote/)

 おすすめ:

* [darling]

[darling]: https://docs.rs/darling/latest/darling/

# メタ的逆引き

## Q. マクロの展開結果を見たい

A. [cargo-expand](https://github.com/dtolnay/cargo-expand) を使います。

```sh
$ cargo install cargo-expand
~~
$ cargo expand --tests test-a
~~
```

ただし derive マクロの出力が構文エラーの場合は、展開結果を [確認できません](https://github.com/dtolnay/cargo-expand/issues/36) 。その間は `trace_macros` を使います:

```rust
//! `lib.rs` で `trace_macros` を有効化
#![feature(trace_macros)]

// このファイルのマクロ展開結果を見る
trace_macros!(true);
pub mod inspect;
trace_macros!(false);

pub mod visit;
```

## Q. Derive マクロのテストがしたい

以下のような構成を想定します:

```sh
.
├── my-crate
└── my-crate-derive
```

`my-crate-derive` の中で `#[derive(MyMacro)]` を使った unit test は書けません:

```rust
#[cfg(test)]
mod test {
    // できない！
    #[derive(MyMacro)]
    pub MyStruct;

    #[test]
    fn test_my_struct() { /* ~~ */ }
}
```

```toml:my-crate-derive/Cargo.toml
[dev-dependencies]
# `src/` 以下で `my-crate` に触れると循環参照になってしまう
my-crate = { path = "../my-crate" }
```

A. Integration test を使います。

```sh
.
├── my-crate
└── my-crate-derive
    ├── src
    └── tests
        └── my_test.rs
```

```toml:my-crate-derive/Cargo.toml
[dev-dependencies]
# `tests/` 以下では循環参照にならない
my-crate = { path = "../my-crate" }
```

`tests/my_test.rs` は独立したクレートとして扱われるため、循環参照が起こりません。

> インテグレーションテストは `tests/it` に集約するのが定石のようです。 ([Delete Cargo Integration Tests](https://matklad.github.io/2021/02/27/delete-cargo-integration-tests.html))

## Q. 外部クレートのアイテムに `#[derive(MyMacro)]` を追加したい

A. fork しない限り不可能です。

`macro_rules!` などを駆使して外部から地道に `MyTrait` を実装することになります。 `serde` の `impls.rs` などが参考になりそうです。

> また orphan rules に反するトレイト実装は作れないことを留意します。

# 実装向け逆引き

## Q. 構造体／列挙体の分類分けは？

A. いずれもフィールドの種類で分離できます。フィールドは 3 種類です:

```rust
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` ブロックを出力します:

```rust
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] を使用します。使用例:

```rust
/// `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>` をパースされている……？