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 리포지토리에서 확인 가능합니다.