ys memos

Blog

Rustで構造体にフィールドを追加するmacroを実装


rust

2023/08/05


Rustには、OOPに見られる継承や、Golangにおける埋め込みは存在しないようで、トレイトによって多態性を実現する。

そして、そのために同じフィールドをいくつかの構造体に持たせるニーズが自分の中であった。

ベタ書きでも良いものの、複数のフィールドの組み合わせを複数の構造体に持たせたい場合は、それが冗長になってしまう。

そこで、フィールドを追加するプロシージャルマクロを実装した。

自分で使う用で開発したものを単純化して記録として残す。


これは実装を始めるまで知らなかったのだが、プロシージャルマクロを書くためには、Cargo.tomlに以下を追加する必要があり、これはつまり、プロシージャルマクロは通常のクレートとは分離されたライブラリとして開発する必要があるということである。

[lib]
proc-macro = true

今回プロシージャルマクロを実現するために、以下のようにプロジェクトを構成した。

repo
  lib    # マクロ定義
  tests  # マクロ利用


Cargo.toml
[package]
name = "macros"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
proc-macro = true

[dependencies]
syn = "2.0"
quote = "1.0"
src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse::Parser, parse_macro_input, DeriveInput};

#[proc_macro_attribute]
pub fn add_name_field(_attrs: TokenStream, input: TokenStream) -> TokenStream {
    let mut ast = parse_macro_input!(input as DeriveInput);

    if let syn::Data::Struct(ref mut struct_data) = ast.data {
        let tokens = quote! {
            pub name: String
        };
        match &mut struct_data.fields {
            syn::Fields::Named(fields) => {
                fields
                    .named
                    .push(syn::Field::parse_named.parse2(tokens.into()).unwrap());
            }
            _ => (),
        }
    } else {
        panic!("`add_field` has to be used with structs ")
    }

    quote! {#ast}.into()
}

Cargo.toml
[package]
name = "tests"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
macros = { path = "../macros" }
src/main.rs
use macros::add_name_field;

#[add_name_field]
#[derive(Debug)]
struct Animal {}

fn main() {
    let animal = Animal {
        name: "mypet".into(),
    };
    println!("{animal:?}");
}

これに加えてGetterも自動実装させたら便利そうですね!


関連タグを探す