Rustのマクロで構造体のフィールド名を取得する

# 概要

構造体(struct)のフィールド名が必要になったので作りました。

次のような用途を想定しています。

* 設定をConfig的な構造体にまとめたい
* 設定ファイルや環境変数から読み込んだ時に、全ての値が定義されているか検証したい
* $フィールド名 \subset 設定ファイル等$

TL;DR の後にマクロの作り方も書いています。

環境: rust 1.41

# TL; DR

```rust
macro_rules! make_struct {
  (@collect_attrs_flag
   ($(#[$attrs:meta])*)
   #[$attr:meta]
   $($tail:tt)*
  ) => {
    make_struct! {
      @collect_attrs_flag
        ($(#[$attrs])* #[$attr])
        $($tail)*
    }
  };

  (@collect_attrs_flag
    ($(#[$attr:meta])*)
    $pub:vis struct $name:ident {
      $($fpub:vis $field:ident: $type:ty,)*
    }) => {
    $(#[$attr])*
      $pub struct $name {
        $($fpub $field: $type,)*
      }
    impl $name {
      fn fields() -> Vec<&'static str> {
        vec![$(stringify!($field)),*]
      }
    }
  };

  ($($tt:tt)*) => {
    make_struct! {@collect_attrs_flag () $($tt)* }
  };
}
make_struct! {
  #[derive(Debug, Clone)]
  #[allow(dead_code)]
  pub struct S {
    pub a: String,
    b: String,
  }
}

fn main() {
  println!("{:?}", S::fields());
  // ["a", "b"]
}


```

# 作り方
## 1. 単に構造体を作る例

マクロを使用する予定の構造体が1つの場合はとても簡単です。
pubにしたければ` struct` を` pub struct`に置換して、フィールド名の頭にpubをつければ良いです。

```rust
macro_rules! make_struct {
  (struct $name:ident { // name: 構造体の名称 識別子なのでident
    $($field:ident: $type:ty,)* // field: フィールド名 識別子, type: タイプなのでty。 繰り返しがあるので$()* 
  }) => {
    struct $name {
      $($field: $type,)*
    }
    impl $name {
      fn fields() -> Vec<&'static str> {
        vec![$(stringify!($field)),*]
      }
    }
  };
}
make_struct! {
  struct S {
    a: String,
    b: String,
  }
}

fn main() {
  for field in S1::fields() {
    println!("{}", field);
  }
}
```


## 2. pub な構造体を許容する例

pubな構造体も許容する場合はメタ変数`vis`を使う必要があります。
これは[macro by example](https://doc.rust-lang.org/reference/macros-by-example.html#metavariables)には載っていますが、[日本語のドキュメント](https://doc.rust-jp.rs/the-rust-programming-language-ja/1.9/book/macros.html#%E6%A7%8B%E6%96%87%E7%9A%84%E3%81%AA%E8%A6%81%E6%B1%82)には見当たりません。

フィールド名にも適用すると、一部だけpubな構造にも対応できます。

```rust
macro_rules! make_struct {
  ($pub:vis struct $name:ident { // vis: visibilityのフラグ指定子
    $($fpub:vis $field:ident: $type:ty,)*
  }) => {
    $pub struct $name {
      $($fpub $field: $type,)*
    }
    impl $name {
      fn fields() -> Vec<&'static str> {
        vec![$(stringify!($field)),*]
      }
    }
  };

}
make_struct! {
  pub struct S {
    pub a: String,
    b: String,
  }
}

fn main() {
  for field in S::fields() {
    println!("{}", field);
  }
}
```

## 3. #[derive()]とかを許容する例

この場合は少しややこしくなります。
先程までの例を元に考えると、次のような実装が思いつきますが、これはコンパイルエラーになります。

```rust
macro_rules! make_struct {
  (
    ($(#[$attr:meta])*) // メタ変数 metaでアトリビュートをキャッチして
    $pub:vis struct $name:ident {
      $($fpub:vis $field:ident: $type:ty,)*
    }) => {
    $(#[$attr])* // 吐き出す
      $pub struct $name {
        $($fpub $field: $type,)*
      }
    impl $name {
      fn fields() -> Vec<&'static str> {
        vec![$(stringify!($field)),*]
      }
    }
  };

}
make_struct! {
  #[derive(Debug, Clone)]
  #[allow(dead_code)]
  pub struct S {
    pub a: String,
    b: String,
  }
}
// error: no rules expected the token `#`
```

`#が期待されるマクロルールが無い`とのことなので一旦`tt`でトークンツリーとしてキャッチすれば良いです。

```rust
macro_rules! make_struct {
  // A. アトリビュートが1つ以上存在する場合はこっちで評価される
  (($(#[$attrs:meta])*)
   #[$attr:meta]
   $($tail:tt)*
  ) => {
    make_struct! {
      ($(#[$attrs])* #[$attr])
      $($tail)*
    }
  };

  // B. アトリビュートが0個(元々0個または手順Aで評価され尽くして0個)の場合はこっち
  (($(#[$attr:meta])*)
   $pub:vis struct $name:ident {
     $($fpub:vis $field:ident: $type:ty,)*
   }) => {
    $(#[$attr])*
      $pub struct $name {
        $($fpub $field: $type,)*
      }
    impl $name {
      fn fields() -> Vec<&'static str> {
        vec![$(stringify!($field)),*]
      }
    }
  };

  // C. 一旦ttで受ける
  ($($tt:tt)*) => {
    make_struct! { () $($tt)* } // 前半の()が無いと全てにマッチして無限ループする
  };
}

make_struct! {
  #[derive(Debug)]
  #[derive(Clone)]
  #[allow(dead_code)]
  pub struct S {
    pub a: String,
    b: String,
  }
}

fn main() {
  println!("{:?}", S::fields());
}
// ["a", "b"]
```

マクロは上から評価されるので(_本当に？_)、フローは次の通りです。

1. 初期状態ではA,Bでは受けられないので、Cの部分で評価されます。（_なんで？_）
2. 2回目以降の`make_struct!`では、アトリビュートが残っている場合、Aで評価されます。Aの方が先にあるし、どの道Bでは評価できないと思われます(`上記の#が期待されるマクロルールが無い`エラー)。
3. アトリビュートが評価され尽くすとBで評価されてstructとして吐き出されます。

このフローは恐らく正しくて、次のようにするとmake_struct!が何回呼ばれているか確認できます。

```bash
rustup default nightly && rustc -Z unstable-options --pretty expanded,hygiene src/main.rs
```

ちなみにこのマクロを書くにあたって[Reddit - macro_rules! make it hard to destruct Rust structs.](https://www.reddit.com/r/rust/comments/6i6cfl/macro_rules_make_it_hard_to_destruct_rust_structs/)を参考にしたのですが、そちらでは`@collect_attrs`を使っています。
こいつは恐らく[分岐Cに到達したことのマーカーな気がする](http://danielkeep.github.io/tlborm/book/pat-internal-rules.html)のですが、正しいかどうか分かりません。一応、TL;DRには残しています。

色々わからないことが多いです。。(´・ω・｀)