Rust의 선언적 매크로로 구조체 생성 도구 개발

Rust에서 여러 구조체 정의 시 반복되는 어트리뷰트와 필드를 처리하기 위한 커스텀 매크로 개발 사례를 소개합니다.

공통 어트리뷰트 문제

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct ActionA {
    url: String,
    version: String,
    a: u64,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[custom_attr]
struct ActionB {
    url: String,
    version: String,
    b: bool,
}

여러 Action 구조체에 공통된 #[derive(...)] 어트리뷰트와 url, version 필드가 반복됩니다. 이러한 중복을 줄이기 위해 매크로를 설계했습니다.

기본 사용법

generate_structs! {
	(
    	#[derive(Debug)]
        #[derive(Clone)]
    ),

    struct A;

    #[derive(Copy)]
    struct B;
}

이 코드는 다음과 같이 전개됩니다:

#[derive(Debug)]
#[derive(Clone)]
struct A;

#[derive(Debug)]
#[derive(Clone)]
#[derive(Copy)]
struct B;

고급 사용법

공통 파라미터 처리를 위한 트레이트 정의:

trait SharedParameters {
    fn url(&self) -> String { "https://example.com".to_string() }
    fn version(&self) -> String { "v1.0.0".to_string() }
}

모든 Action 구조체에 해당 트레이트를 구현하고, 필요 시 오버라이드할 수 있도록 설계했습니다.

generate_structs! {
	(
    	callback_macro, // 콜백 매크로
        #[derive(Debug)]
    ),

    #[derive(Clone)]
    struct A;

	@[version = "v2.3.4".to_string()]
    #[derive(serde::Serialize)]
    struct B;

    @[url = "https://crates.io".to_string()]
    struct C;
}

매크로 내부 구현

macro_rules! callback_macro {
	(
    	$(
        	$(#[$meta: meta])*
            $(@[$($my_meta: tt)*])*
            $vis: vis struct $name: ident $body: tt
        )*
    ) => {
    	$(
            $(#[$meta])*
            $vis struct $name $body

            impl SharedParameters for $name {
                $(
                	override_method!{ $($my_meta)* };
                )*
            }
        )*
    };
}

macro_rules! override_method {
	(url = $expr: expr) => {
    	fn url(&self) -> String { $expr }
    };
	(version = $expr: expr) => {
        fn version(&self) -> String { $expr }
    };
    ($($tt: tt)*) => {
        compile_error!("지원되지 않는 파라미터");
    };
}

최종 결과

#[derive(Debug)]
#[derive(Clone)]
struct A;

impl SharedParameters for A {}

#[derive(Debug)]
#[derive(serde::Serialize)]
struct B;

impl SharedParameters for B {
    fn version(&self) -> String { "v2.3.4".to_string() }
}

#[derive(Debug)]
struct C;

impl SharedParameters for C {
    fn url(&self) -> String { "https://crates.io".to_string() }
}

이 매크로는 약 300줄의 코드로 구성되어 있으며, 복잡한 선언적 매크로 개발의 도전성을 보여줍니다. 자세한 내용은 GitHub 리포지토리에서 확인 가능합니다.

태그: Rust declarative macros trait macro_rules struct

7월 4일 18:37에 게시됨