Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

13. 매크로: 코드를 짜는 코드 🟡

학습 목표:

  • 패턴 매칭과 반복을 이용한 **선언적 매크로(macro_rules!)**를 작성합니다.
  • 매크로를 사용해야 할 때와 제네릭/트레이트로 충분할 때를 구분합니다.
  • 절차적 매크로(Procedural Macros): derive, attribute, function-like 세 가지 유형을 이해합니다.
  • synquote 크레이트를 활용해 커스텀 Derive 매크로를 구현해 봅니다.

선언적 매크로 (macro_rules!)

선언적 매크로는 코드의 구문 패턴을 매칭하여 컴파일 타임에 코드를 확장합니다.

#![allow(unused)]
fn main() {
// 간단한 HashMap 생성 매크로
macro_rules! hashmap {
    ( $( $key:expr => $value:expr ),* $(,)? ) => {
        {
            let mut map = std::collections::HashMap::new();
            $( map.insert($key, $value); )*
            map
        }
    };
}

let scores = hashmap! {
    "Alice" => 95,
    "Bob" => 87,
};
}

매크로 프래그먼트 (Fragment) 종류

매크로에서 매칭할 수 있는 코드 조각의 타입들입니다.

이름매칭 대상예시
$x:expr모든 표현식42, a + b, foo()
$x:ty타입i32, Vec<String>
$x:ident식별자 (변수/함수명)my_var, Config
$x:path경로std::io::Error
$x:tt단일 토큰 트리가장 유연하며 무엇이든 매칭 가능

매크로 사용 시 주의사항

  • 권장 상황: 트레이트/제네릭으로 해결하기 힘든 보일러플레이트 제거(테스트 케이스 자동 생성 등), DSL 구축(html!, sql!).
  • 지양 상황: 단순한 함수나 제네릭으로 해결 가능한 경우. 매크로는 디버깅이 어렵고 자동 완성이 잘 작동하지 않습니다.

절차적 매크로 (Procedural Macros)

절차적 매크로는 토큰 스트림을 입력받아 Rust 함수처럼 조작하여 새로운 토큰 스트림을 반환합니다.

  • Derive 매크로: #[derive(MyTrait)] 처럼 구조체나 열거형에 트레이트 구현을 자동으로 추가합니다.
  • Attribute 매크로: #[route(GET, "/")] 처럼 아이템 자체를 변형합니다.
  • Function-like 매크로: sql!(SELECT * FROM ...) 처럼 커스텀 구문을 정의합니다.

매크로 위생(Hygiene)과 $crate

매크로 내부에서 정의한 변수 이름이 매크로를 호출한 곳의 변수 이름과 충돌하지 않도록 보장하는 것을 위생이라고 합니다. 또한 라이브러리에서 매크로를 정의할 때는 사용자가 크레이트를 어떤 이름으로 임포트했든 상관없이 작동하도록 항상 $crate 키워드를 사용하여 자신을 참조해야 합니다.


📝 연습 문제: 선언적 매크로 map! 제작 ★ (~15분)

키-값 쌍을 인자로 받아 HashMap을 생성하는 map! 매크로를 작성해 보세요. 쉼표가 마지막에 있거나 아예 인자가 없는 상황(map!{})도 지원해야 합니다.


📌 요약

  • 단순 반복 코드는 **macro_rules!**로 해결하세요.
  • 복잡한 구조체 분석이나 코드 생성에는 **syn**과 **quote**를 활용한 절차적 매크로가 적합합니다.
  • 매크로를 설계할 때는 항상 함수나 트레이트로 더 나은 해결책이 있는지 먼저 고민하세요.
  • 라이브러리용 매크로에서는 호환성을 위해 반드시 **$crate**를 사용하세요.