Rust deriveマクロを軽くハンズオン


こちらの記事は [Rustマクロ冬期講習アドベントカレンダー](https://qiita.com/advent-calendar/2024/rust-macro) 21日目の記事です！

> アドカレまとめ記事はこちら！: [Rustマクロ作成チートシート！](https://qiita.com/namn1125/items/d601d2cbbf2cc7b5d3dc)

[前前回](https://qiita.com/namn1125/items/648e915cad62f21656c0)は関数風マクロについて、 [前回](https://qiita.com/namn1125/items/28d2e95090ddf7fbc7e0) は属性風マクロについて軽いハンズオンを行いました。今回はderiveマクロに取り組みます！

:::note
**deriveマクロの定義・構造**

`Cargo.toml` に、 `[lib] proc-macro = true` という項目を追加した上で、 `src/lib.rs` に、

```rust:src/lib.rs
use proc_macro::TokenStream;

#[proc_macro_derive(Deriveマクロの名前, attributes(関連する不活性属性))]
pub fn deriveマクロの実装_名前は呼び出しに関係なし(item: TokenStream) -> TokenStream {
    // ...
}
```

というように `#[proc_macro_derive(Deriveマクロの名前)]` を付けた関数を定義すると、 `#[derive(Deriveマクロの名前)]` みたいに使用できるderiveマクロが定義されます。

deriveマクロは構造体・列挙体にのみ使用できます。

入力の `item: `[`TokenStream`](https://doc.rust-lang.org/beta/proc_macro/struct.TokenStream.html) を元にして三種の神器クレート [syn](https://docs.rs/syn/latest/syn/) [quote](https://docs.rs/quote/latest/quote/) [proc-macro2](https://docs.rs/proc-macro2/latest/proc_macro2/) を使い出力を作るところまでは他のマクロと同様です。しかし、属性風マクロは入力部分が置換されるのに対してderiveマクロは **追記** が行われます[^derive]。

また、deriveマクロでは構造体・列挙体全体や各フィールド・バリアントに属性風マクロ的な何か(本記事の例だと `#[enum_display(rename_all = "snake_case")]` など)を併用することがあります。こちらはマクロ定義においては `#[proc_macro_derive(..., attributes(enum_display))]` の `attributes` で指定され、この属性自体がマクロとして動作するわけではない(deriveマクロによって初めて拾われる)ので **不活性属性** (inert attribute)と呼ばれます。
:::

[^derive]: deriveマクロの和訳には「導出マクロ」「派生マクロ」があります。「派生マクロ」の方だとオブジェクト指向の派生クラスと混同するというか似た概念と捉えられそうなので、避けたい和訳です。一方で「導出マクロ」だと入力となる構造体・列挙体から実装内容を「導出する」ため和訳としてあっていそうです！

# deriveマクロ「 `EnumDisplay` 」

ド定番な車輪ですが、列挙体に [`std::fmt::Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html) を実装する `EnumDisplay` マクロを作ってみたいと思います！(あまり複雑にしたくないためフィールドを持つ列挙体や構造体は受け取らないものとします。)

```rust:Rust
#[derive(EnumDisplay)]
#[enum_display(rename_all = "snake_case")]
enum Hoge {
    Fuga,
    #[enum_display(rename = "barbar")]
    Bar,
}
```

こう書くと列挙体に [`std::fmt::Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html) が自動実装されることを目指します！

```rust:Rust
// ...省略...

impl ::std::fmt::Display for Hoge {
    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        match self {
            Self::Fuga => write!(f, "fuga"),
            Self::Bar => write!(f, "barbar"),
        }
    }
}

fn main() {
    println!("{} {}", Hoge::Fuga, Hoge::Bar);
    // hoge barbar
}
```

列挙体名の上には `#[enum_display(rename_all = "...")]` みたいな属性を、バリアント( `Fuga` や `Bar` のこと )には `#[enum_display(rename = "...")]` みたいな属性を付けられるようにします。

| 属性 | 付与対象 | 設定可能値 | 効果 |
|:----|:--------|:---------|:----|
| `rename_all` | 列挙体名 | `snake_case`, `camelCase` 等 | 全バリアントのデフォルト出力を指定したケースにします。 |
| `rename` | バリアント | 任意の文字列 | 指定した名前で出力するようにします。 |

このような属性は、属性風マクロとは異なるものの、deriveマクロを補助するために使われます。この属性自体はマクロのような効果はないため **不活性属性** (inert attribute) と呼ばれます。(そしてそのため属性風マクロは活性属性 (active attribute) と呼ばれます。)

# 手順 1. 骨組みを作る

[RTA](https://qiita.com/namn1125/items/b05ad80d324afcd2ed91)と同様に準備します！

```bash
cargo new --lib enum_display
```

```diff_toml:Cargo.toml
[package]
name = "enum_display"
version = "0.1.0"
edition = "2021"

+[lib]
+proc-macro = true

[dependencies]
```

ここまでのマクロたち([関数風マクロ](https://qiita.com/namn1125/items/648e915cad62f21656c0)、[属性風マクロ](https://qiita.com/namn1125/items/28d2e95090ddf7fbc7e0))同様扱いにくい `proc-macro` クレートと縁を切るため、 `src/lib.rs` はシンプルに保ち実装は `src/impls.rs` で行うディレクトリ構造にします。

```plain:ディレクトリ構造
.
├── Cargo.lock
├── Cargo.toml
├── src
│   ├── impls.rs
│   ├── lib.rs
│   └── main.rs
└── target
```

ここまでは前回までとほぼ同じで、そして例によってマクロの宣言方法が異なります。

```rust:src/lib.rs
mod impls;

use proc_macro::TokenStream;

#[proc_macro_derive(EnumDisplay, attributes(enum_display))]
pub fn enum_display_impl(item: TokenStream) -> TokenStream {
    todo!()
}
```

deriveマクロの入力は属性風マクロとは異なり一つです！属性風マクロでは属性部分を第一引数 `attr` として、アイテム部分を第二引数 `item` として受け取っていましたが、deriveマクロでは `item` しか受け取りません。では不活性属性部分はどうやって受け取ればよいか？は後程わかります！

続きです。三種の神器を `cargo add` しておきます。

```bash
cargo add proc-macro2 quote
cargo add syn --features full --features extra-traits
```

# 手順 2. 入力をパースする

今回の入力パースは関数風マクロや属性風マクロとは打って変わって固定的です！

```rust:Rust
let input = parse_macro_input!(item as DeriveInput);
```

組み込むと次のような感じです。

```rust:src/lib.rs
mod impls;

use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(EnumDisplay, attributes(enum_display))]
pub fn enum_display_impl(item: TokenStream) -> TokenStream {
    let _input = parse_macro_input!(item as DeriveInput);

    TokenStream::new()
}
```

関数風マクロや属性風マクロでは欲しい入力を受け取れるようにこちらで色々指定したり用意したりしましたが、deriveマクロでは名前そのままの [syn::DeriveInput](https://docs.rs/syn/latest/syn/struct.DeriveInput.html) を入力として利用します！

```rust:Rust
pub struct DeriveInput {
    pub attrs: Vec<Attribute>, // 構造体・列挙体の上に付与されている属性
    pub vis: Visibility, // 構造体・列挙体の可視性
    pub ident: Ident, // 構造体・列挙体の名前
    pub generics: Generics, // 構造体・列挙体に存在するジェネリクス
    pub data: Data, // フィールドやバリアントの情報
}
```

今回は、列挙体名を得るために `ident` を、列挙体に付与された不活性属性の取得目的で `attrs` を、そしてバリアントを扱うために `data` フィールドを主に利用します。

:::note
つまり不活性属性は `DeriveInput` に含まれているので、使いたい時に勝手に使う感覚でおｋです！
:::

# 手順 3. パースをしつつ出力を考える

バリアントと付与されている属性情報があれば入力を決定できます。ただ、入力された属性は引き続き適切にパースして中身を取り出す必要があります。

とりあえず `impls.rs` 側の関数で `DeriveInput` を受け取るようにして続行していきましょう！

まずは `DeriveInput` から使用するフィールドのみ抜き出し、また対象が列挙体であることを確認します。

付与対象が構造体か列挙体か共用体かは、 `data` フィールドにて[`syn::Data`](https://docs.rs/syn/latest/syn/enum.Data.html) 列挙体により表されているので、 `let-else` 文のパターンマッチで調べています。(共用体は普通にRustプログラミングをしている場合は不要なので忘れてもらって大丈夫です。)

```rust:src/impls.rs
use proc_macro2::TokenStream;
use syn::{spanned::Spanned, Data, DeriveInput};

pub fn enum_display_impl(input: DeriveInput) -> syn::Result<TokenStream> {
    let span = input.span(); // コンパイルエラー時に使う情報
    let DeriveInput { // let文で分割
        attrs,
        data,
        ident: enum_ident,
        ..
    } = input;

    let Data::Enum(enm) = data else {
        // 構造体や共用体の時はコンパイルエラーにする
        return Err(syn::Error::new(span, "expected enum"));
    };

    todo!()
}
```

ここから各バリアントごとに `match` 式のアームを作っていくのですが、先に列挙体に付与された属性の情報を持つ `attrs: Vec<Attributes>` から `rename_all` の情報がないかを抜き出しておきます。この抜き出しは愚直に行います。

今回 `rename_all` に指定できるケース一覧は次の通りにしたいと考えています。

| ケース    | 説明       |
|:---------|:----------|
|UPPERCASE |全て大文字に |
|lowercase |全て小文字に |
|camelCase |キャメルケース|
|PascalCase|パスカルケース|
|snake_case|スネークケース|
|kebab-case|ケバブケース  |

文字列のケース変換を自前実装するのは面倒なので今回はクレートに頼りましょう。

- [convert_case](https://docs.rs/convert_case/latest/convert_case/)

```bash
cargo add convert_case
```

ケース名から [`convert_case::Case`](https://docs.rs/convert_case/latest/convert_case/enum.Case.html) に変換する処理は軽く探した感じ提供されてなさそうだったので、ここだけ予め準備しておきます。

```rust:Rust
use convert_case::Case;

fn str2case(s: &str) -> Result<Case, ()> {
    match s.to_ascii_lowercase().as_str() {
        "upper" | "uppercase" | "upper_case" => Ok(Case::Upper),
        "lower" | "lowercase" | "lower_case" => Ok(Case::Lower),
        "camel" | "camelcase" | "camel_case" => Ok(Case::Camel),
        "pascal" | "pascalcase" | "pascal_case" => Ok(Case::Pascal),
        "snake" | "snakecase" | "snake_case" => Ok(Case::Snake),
        "kebab" | "kebabcase" | "kebab_case" => Ok(Case::Kebab),
        _ => Err(()),
    }
}
```

下準備は整ったので、 `attrs: Vec<Attribute>` から全バリアントのデフォルトケースを決める `rename_all` を `Case` で抜き出します！当然指定されていない場合もあるので返り値シグネチャは `Option<Case>` です。

```rust:Rust
use convert_case::Case;
use syn::{Attribute, Expr, ExprLit, Lit, Meta, MetaList, MetaNameValue};

fn get_case(attrs: Vec<Attribute>) -> Option<Case> {
    let mut res = None;

    // 属性は複数付いている可能性があるのでVecで渡されている
    // #[hoge(...)] みたいなやつごとに繰り返し
    // meta 以外のフィールドは不要
    for Attribute { meta, .. } in attrs {
        // Path: meta
        // List: meta( ... )
        // NameValue: meta = ...
        // のうち MetaList 以外の時は対象外としてcontinue
        let Meta::List(MetaList { path, tokens, .. }) = meta else {
            continue;
        };
        // enum_display(rename_all = "snake_case") のうち
        // path には enum_display が
        // tokens には rename_all = "snake_case" が入る 

        // enum_display 以外の属性は対象外として continue (1)
        if !path.is_ident("enum_display") {
            continue;
        }

        // rename_all = "snake_case" を
        // path: rename_all
        // value: "snake_case"
        // に分解
        //
        // もし enum_display(aaa) みたいに異なるものがある場合 continue (2)
        let Ok(MetaNameValue { path, value, .. }) = syn::parse2(tokens) else {
            continue;
        };

        // path が rename_all ではない場合 continue
        if !path.is_ident("rename_all") {
            continue;
        }

        // rename_all = 10 みたいになっている時 continue (2)
        let Expr::Lit(ExprLit {
            lit: Lit::Str(s), ..
        }) = value
        else {
            continue;
        };

        // "snake_case" -> Case::Snake
        res = str2case(&s.value()).ok();
    }

    res
}
```

- (1): `enum_display` 以外の属性もこの繰り返しに入ってくるため、 `path` の中身の確認は必要です。実際に `#[allow(unused)]` 等の適当な属性を付けて `dbg!(&path)` で確認してみると `attrs: Vec<Attribute>` に含まれていることがわかります
- (2): `continue` にするよりもエラーに倒した方が利用者側には便利かもしれません。ただ `Result<Option<Case>>` は煩雑だと考えたことと、他の別なマクロがたまたま同じ名前の不活性属性を利用している時にもエラーになってしまうことを鑑みて(杞憂)、 `continue` する方に倒しました

:::note
**let-else文大活躍！**

こういう `Result` 型や `Option` 型以外の型でかつ `match` 式だと多少煩雑になってしまいそうな場合には [`let-else` 文](https://doc.rust-lang.org/rust-by-example/flow_control/let_else.html) が便利です！

別に `match` 式でも浅くはできますが、特にネストを深めないことで可読性に貢献しています。
:::

ケースが無事取得できたら後は各バリアントごとにアームを作り、最後に `match` 式を組み立てて完成です。 [`DataEnum` の `variants` フィールド](https://docs.rs/syn/latest/syn/struct.DataEnum.html#structfield.variants) でバリアントのイテレータを取得できますので、これで各バリアントの出力を作ります。

長いですが新しい話題は少ないので、一気に書きたいと思います！

```rust:src/impls.rs
use convert_case::{Case, Casing};
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{
    spanned::Spanned, Attribute, Data, DeriveInput, Expr, ExprLit, Fields, Lit, Meta, MetaList,
    MetaNameValue, Variant,
};

pub fn enum_display_impl(input: DeriveInput) -> syn::Result<TokenStream> {
    // ...省略...

    // rename_allが設定されていない場合は列挙体のバリアント表現に等しいパスカルケースにする
    let rename_all = get_case(attrs).unwrap_or(Case::Pascal);

    // 出力
    let tokens = enm
        .variants
        .into_iter()
        .map(|variant| { // 各バリアント( Hoge::Fuga, Hoge::Bar )ごと
            let span = variant.span(); // バリアント部分のSpan
            // 必要なフィールドのみ抜き取る
            let Variant {
                attrs, // 各バリアントの属性
                ident, // 各バリアントの名前
                fields: Fields::Unit, // Hogeのみ受け取り、Hoge(...) や Hoge {...} といったバリアントは許さない
                ..
            } = variant
            else {
                return Err(syn::Error::new(span, "expected unit variant only"));
            };

            // #[enum_display(rename = "...")] が指定されているかで分岐
            let name = match get_name(attrs) {
                // 指定されている場合はそちらで確定
                Some(name) => name.to_token_stream(),
                // 指定されていない場合
                None => {
                    // 名前を rename_all で指定されたケースに変換
                    let ident = ident.to_string().to_case(rename_all);
                    ident.to_token_stream()
                }
            };

            // アームの出力 (1)
            Ok(quote! {
                Self::#ident => write!(f, #name),
            })
        })
        .collect::<syn::Result<Vec<TokenStream>>>()?;

    // この時点で
    // tokens = vec![
    //     Self::Fuga => write!(f, "fuga"),
    //     Self::Bar => write!(f, "barbar"),
    // ];
    // のようになっている

    // 全体の出力 (1)
    Ok(quote! {
        // 衛生性のためフルパス表示 (2)
        impl ::std::fmt::Display for #enum_ident {
            fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
                match self {
                    // アームが展開される
                    #(
                        #tokens
                    )*
                }
            }
        }
    })
}

// 各バリアントの rename 指定を抽出
// 最後以外は get_case とほぼ同じ
fn get_name(attrs: Vec<Attribute>) -> Option<String> {
    let mut res = None;

    for Attribute { meta, .. } in attrs {
        let Meta::List(MetaList { path, tokens, .. }) = meta else {
            continue;
        };
        if !path.is_ident("enum_display") {
            continue;
        }

        let Ok(MetaNameValue { path, value, .. }) = syn::parse2(tokens) else {
            continue;
        };

        if !path.is_ident("rename") {
            continue;
        }

        let Expr::Lit(ExprLit {
            lit: Lit::Str(s), ..
        }) = value
        else {
            continue;
        };

        res = Some(s.value());
    }

    res
}
```

- (1): [`quote::quote!` マクロ](https://docs.rs/quote/latest/quote/macro.quote.html) を使って、希望通りの出力を構成
- (2): マクロの出力で登場するインポートパスは、外部の影響を極力受けないようにフルパスで記述する

[前回](https://qiita.com/namn1125/items/28d2e95090ddf7fbc7e0)・[前々回](https://qiita.com/namn1125/items/648e915cad62f21656c0)では何故か運よく？使用しませんでしたが、手続きマクロ実装において出力を作るために必須な **[`quote::quote!` マクロ](https://docs.rs/quote/latest/quote/macro.quote.html)** を使用しています。

```rust:Rust
Ok(quote! {
    impl ::std::fmt::Display for #enum_ident {
        fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
            match self {
                #(
                    #tokens
                )*
            }
        }
    }
})
```

`quote!` マクロを使うと、あたかも[宣言マクロ](https://qiita.com/namn1125/items/63756af0d3cecdaa02cd)と同じような記法で [`proc_macro2::TokenStream`](https://docs.rs/proc-macro2/latest/proc_macro2/struct.TokenStream.html) を構成できます。違いは `$` が `#` に変わったぐらいでしょうか？ `#name` みたいに `#変数` でアクセスできるかはその値が [`ToTokens`](https://docs.rs/quote/latest/quote/trait.ToTokens.html) トレイトを実装した型かどうかで決まります。 `syn` クレート以下の構文要素には軒並み実装されています。

`::std::fmt::Display` の部分を `::` から始める丁寧な書き方で表現しているのは、パスの曖昧さを排除して衛生性を保つためです。下手に `fmt::Display` 等途中から書く書き方になっていると、マクロ前後の `use` の状況に左右され、実装される `Display` が意図したものと異なってしまう可能性があることより、フルパスで書いています。

衛生性について詳しくは別な記事にまとめたのでそちらを読んでほしいです！

[Rustマクロの事前知識③「マクロのマナー 衛生性(健全性)」 #Rust - Qiita](https://qiita.com/namn1125/items/61587afa264107ba7567)

https://qiita.com/namn1125/items/61587afa264107ba7567

# まとめ・所感

これでマクロはほぼ完成です(あとは `src/lib.rs` の微修正のみ)！今回はやたら属性のパースが長かっただけで処理内容はシンプルでした。

最終的な全体像は次の通りになります。

```rust:src/lib.rs
mod impls;

use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(EnumDisplay, attributes(enum_display))]
pub fn enum_display_impl(item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as DeriveInput);

    impls::enum_display_impl(input)
        .unwrap_or_else(syn::Error::into_compile_error)
        .into()
}
```

```rust:src/impls.rs
use convert_case::{Case, Casing};
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{
    spanned::Spanned, Attribute, Data, DeriveInput, Expr, ExprLit, Fields, Lit, Meta, MetaList,
    MetaNameValue, Variant,
};

pub fn enum_display_impl(input: DeriveInput) -> syn::Result<TokenStream> {
    let span = input.span();
    let DeriveInput {
        attrs,
        data,
        ident: enum_ident,
        ..
    } = input;

    let Data::Enum(enm) = data else {
        return Err(syn::Error::new(span, "expected enum"));
    };

    let rename_all = get_case(attrs).unwrap_or(Case::Pascal);

    // 出力
    let tokens = enm
        .variants
        .into_iter()
        .map(|variant| {
            let span = variant.span();
            let Variant {
                attrs,
                ident,
                fields: Fields::Unit,
                ..
            } = variant
            else {
                return Err(syn::Error::new(span, "expected unit variant only"));
            };

            let name = match get_name(attrs) {
                Some(name) => name.to_token_stream(),
                None => {
                    let ident = ident.to_string().to_case(rename_all);
                    ident.to_token_stream()
                }
            };

            Ok(quote! {
                Self::#ident => write!(f, #name),
            })
        })
        .collect::<syn::Result<Vec<TokenStream>>>()?;

    Ok(quote! {
        impl ::std::fmt::Display for #enum_ident {
            fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
                match self {
                    #(
                        #tokens
                    )*
                }
            }
        }
    })
}

fn get_case(attrs: Vec<Attribute>) -> Option<Case> {
    let mut res = None;

    for Attribute { meta, .. } in attrs {
        let Meta::List(MetaList { path, tokens, .. }) = meta else {
            continue;
        };

        if !path.is_ident("enum_display") {
            continue;
        }

        let Ok(MetaNameValue { path, value, .. }) = syn::parse2(tokens) else {
            continue;
        };

        if !path.is_ident("rename_all") {
            continue;
        }

        let Expr::Lit(ExprLit {
            lit: Lit::Str(s), ..
        }) = value
        else {
            continue;
        };

        res = str2case(&s.value()).ok();
    }

    res
}

fn str2case(s: &str) -> Result<Case, ()> {
    match s.to_ascii_lowercase().as_str() {
        "upper" | "uppercase" | "upper_case" => Ok(Case::Upper),
        "lower" | "lowercase" | "lower_case" => Ok(Case::Lower),
        "camel" | "camelcase" | "camel_case" => Ok(Case::Camel),
        "pascal" | "pascalcase" | "pascal_case" => Ok(Case::Pascal),
        "snake" | "snakecase" | "snake_case" => Ok(Case::Snake),
        "kebab" | "kebabcase" | "kebab_case" => Ok(Case::Kebab),
        _ => Err(()),
    }
}

fn get_name(attrs: Vec<Attribute>) -> Option<String> {
    let mut res = None;

    for Attribute { meta, .. } in attrs {
        let Meta::List(MetaList { path, tokens, .. }) = meta else {
            continue;
        };
        if !path.is_ident("enum_display") {
            continue;
        }

        let Ok(MetaNameValue { path, value, .. }) = syn::parse2(tokens) else {
            continue;
        };

        if !path.is_ident("rename") {
            continue;
        }

        let Expr::Lit(ExprLit {
            lit: Lit::Str(s), ..
        }) = value
        else {
            continue;
        };

        res = Some(s.value());
    }

    res
}
```

```rust:src/main.rs
use enum_display::EnumDisplay;

#[derive(EnumDisplay)]
#[enum_display(rename_all = "snake_case")]
enum Hoge {
    Fuga,
    #[enum_display(rename = "barbar")]
    Bar,
}

fn main() {
    println!("{} {}", Hoge::Fuga, Hoge::Bar);
}
```

`cargo expand` の結果は次のようになり、

```rust:cargo expand
$ cargo expand --bin enum_display
    Checking enum_display v0.1.0 (/path/to/enum_display)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
use enum_display::EnumDisplay;
#[enum_display(rename_all = "snake_case")]
enum Hoge {
    Fuga,
    #[enum_display(rename = "barbar")]
    Bar,
}
impl ::std::fmt::Display for Hoge {
    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
        match self {
            Self::Fuga => f.write_fmt(format_args!("fuga")),
            Self::Bar => f.write_fmt(format_args!("barbar")),
        }
    }
}
fn main() {
    {
        ::std::io::_print(format_args!("{0} {1}\n", Hoge::Fuga, Hoge::Bar));
    };
}
```

実行結果は次の通りになりました！

```plain:実行結果
$ cargo run -q
fuga barbar
```

deriveマクロは入力が [`DeriveInput`](https://docs.rs/syn/latest/syn/struct.DeriveInput.html) である点が特徴的でした！そこから属性をパースしつつ[`quote::quote!`](https://docs.rs/quote/latest/quote/macro.quote.html)マクロを使って出力を作っていきました。

deriveマクロはなんとなく実装が大変そうなイメージがありますが、実装したい処理次第であり結局は他のマクロと大差ありません。おそらく属性のパースが面倒だったのでそのような印象がある気がします。

そこで今後の回では [daring](https://docs.rs/darling/latest/darling/) クレートを取り扱うつもりです！こちらのクレートを使うと今回みたいなマクロはかなりスッキリ書けます！乞うご期待！

ここまでお読みいただきありがとうございました！ :bow: