2023/08/05
はじめに
Rustには、OOPに見られる継承や、Golangにおける埋め込みは存在しないようで、トレイトによって多態性を実現する。
そして、そのために同じフィールドをいくつかの構造体に持たせるニーズが自分の中であった。
ベタ書きでも良いものの、複数のフィールドの組み合わせを複数の構造体に持たせたい場合は、それが冗長になってしまう。
そこで、フィールドを追加するプロシージャルマクロを実装した。
自分で使う用で開発したものを単純化して記録として残す。
前提
これは実装を始めるまで知らなかったのだが、プロシージャルマクロを書くためには、Cargo.toml
に以下を追加する必要があり、これはつまり、プロシージャルマクロは通常のクレートとは分離されたライブラリとして開発する必要があるということである。
[lib]
proc-macro = true
ディレクトリ構造
今回プロシージャルマクロを実現するために、以下のようにプロジェクトを構成した。
repo
lib # マクロ定義
tests # マクロ利用
開発
lib
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()
}
tests
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も自動実装させたら便利そうですね!