Rust의 Macro(매크로) 종류

Rust의 매크로
Declarative Macro (일반 매크로, macro_rules!)
Procedural Macro (프로시저 매크로, proc_macro)
Attribute-like Macro (#[derive(…)], #[test] 같은 것)
Declarative Macro (선언형 매크로, macro_rules!)
사용 목적
반복적인 패턴을 줄이기 위한 규칙 기반 매크로
컴파일 타임에 코드가 치환되는 방식 (ex: C의 #define과 유사함)
예제
macro_rules! say_hello {
() => {
println!("Hello, Rust!");
};
}
fn main() {
say_hello!(); // "Hello, Rust!"가 출력됨
}
Procedural Macro (프로시저 매크로, proc_macro)
사용 목적
입력된 코드(TokenStream)를 분석하고 새로운 코드를 생성하는 매크로
일반적으로 #[derive(…)], #[proc_macro] 같은 매크로를 생성할 때 사용된다.
use proc_macro;
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream {
// 입력을 받아 새로운 코드를 생성하여 반환
input
}
Rust의 코드(TokenStream)을 입력 받아 변환해 반환하는 함수
Declarative Macro와 달리 더 정교한 코드 변환이 가능함
Attribute-like Macro (속성 매크로, #[derive(...)], #[test] 등)
사용 목적
기본적으로 Procedural Macro의 한 형태
구조체, 함수 등에 #[…] 형태로 붙여 특정한 기능을 추가할 때 사용됨
일반적으로 Procedural Macro로 구현된다.
#[derive(…)], #[test], #[cfg(…)] 같은 매크로들이 해당됨
#[test]
fn my_test() {
assert_eq!(2 + 2, 4);
}
#[test]는 Rust의 테스트 시스템에서 자동으로 실행될 함수임을 컴파일러에게 알려준다
cargo test시 이 함수가 자동으로 실행된다.
찾아보다 궁금해진 것
#[derive(Parser)] 매크로의 의미
Kotlin의 implements와 다른 점
kotlin의 implements와 다른 점은 Kotlin은 개발자가 직접 인터페이스를 구현해야한다는 점이다. 하지만 Rust의 #[derive(Parser)] 는 단순 트레이트(인터페이스)만 구현하는게 아니라 관련된 메서드를 컴파일 타임에 자동으로 생성한다.
derive란?
- Rust의 derive는 컴파일 타임에 특정 트레이트를 자동으로 구현하는 기능
#[derive(Debug)]
struct MyStruct {
name: String,
age: u32,
} // Rust 컴파일러가 Debug 트레이트를 자동으로 구현함.
- 내부적으로 아래 코드가 자동으로 추가 된다.
impl std::fmt::Debug for MyStruct {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "MyStruct {{ name: {:?}, age: {:?} }}", self.name, self.age)
}
}
#[derive(Parser)]는 어떻게 동작할까?
Rust의 기본 derive는 Debug, Clone 등 표준 트레이트만 지원함.
하지만 사용자 정의 매크로(예: Parser)를 derive 하려면 Procedural Macro(proc_macro_derive)가 필요하다.
#[derive(Parser)]가 Attribute-like Macro지만 실제로는 Procedural Macro로 구현된 이유
#[derive(Parser)]는 어떻게 Procedural Macro로 구현될까?
#[proc_macro_derive(Parser, attributes(clap))]
pub fn derive_parser(input: TokenStream) -> TokenStream {
clap_derive::parse(input)
}
- #[derive(Parser)]를 만나면, Rust 컴파일러는 proc_macro_derive(Parser, attributes(clap))를 실행한다.
이 코드의 의미
#[proc_macro_derive(Parser, attributes(clap))]
Parser라는 derive 매크로를 정의한다.
사용자가 #[derive(Parser)]를 사용하면, 이 매크로가 실행된다.
derive_parser(input: TokenStream) -> TokenStream
TokenStream을 입력받아 새로운 코드(TokenStream)을 반환한다.
즉, struct Args {}를 입력받아, impl Parser for Args {}를 자동으로 생성한다.
cargo expand를 사용해 매크로가 생성한 코드 확인하기
cargo install cargo-expand
cargo-expand를 설치하고 프로젝트 디렉터리에서 cargo expand 명령어를 실행하면, 매크로가 변환된 결과를 확인할 수 있다.
✅ 결론
#[derive(...)]는 Rust에서 특정 트레이트를 자동으로 구현하는 Attribute-like Macro
기본적으로 #[derive(Debug)], #[derive(Clone)] 같은 표준 트레이트만 지원된다.
#[derive(Parser)] 같은 사용자 정의 derive 매크로를 사용하려면, Procedural Macro(proc_macro_derive)가 필요하다.
#[derive(Parser)]는 proc_macro_derive(Parser, attributes(clap))을 통해 Parser 트레이트를 자동으로 구현하는 Procedural Macro
즉, #[derive(Parser)]는 겉으로 보면 Attribute-like Macro이지만, 내부적으로는 Procedural Macro를 이용하여 트레이트 구현을 자동 생성하는 역할을 한다.



