2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-05-03

Derive マクロのメモです。

リソース

文書

コード

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

一応: 手前味噌1, 手前味噌2, 手前味噌3

主要クレート

必須:

おすすめ:

メタ的逆引き

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() { /* ~~ */ }
}
my-crate-derive/Cargo.toml
[dev-dependencies]
# `src/` 以下で `my-crate` に触れると循環参照になってしまう
my-crate = { path = "../my-crate" }

A. Integration test を使います。

.
├── my-crate
└── my-crate-derive
    ├── src
    └── tests
        └── my_test.rs
my-crate-derive/Cargo.toml
[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 を実装することになります。 serdeimpls.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> をパースされている……?

2
1
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?