매크로

매크로 (Macros)

반복되는 문법을 줄이고 싶거나, 언어 자체의 기능을 확장하고 싶을 때 Rust의 매크로가 쓰입니다. 이름을 붙여 두고 some_extension!(...)처럼 일관된 문법으로 호출한다는 점이 핵심인데요, 이 문서에서 매크로의 정의 방식과 호출 규칙을 정리해 볼게요.

출처: Rust Reference

Rust의 기능과 문법은 매크로라고 부르는 사용자 정의로 확장할 수 있습니다. 매크로는 이름을 가지며, some_extension!(...)이라는 일관된 문법으로 호출돼요.

새 매크로를 정의하는 방법은 두 가지입니다:

  • Macros by Example은 새로운 문법을 보다 선언적이고 상위 수준(high-level)으로 정의합니다.
  • Procedural Macros은 입력 토큰에 대해 동작하는 함수를 이용해 함수형 매크로, 커스텀 derive, 커스텀 속성을 정의합니다.

매크로 호출 (Macro Invocation)

문법 (Syntax)

MacroInvocation → SimplePath ! DelimTokenTree

DelimTokenTree  → ( TokenTree* ) | [ TokenTree* ] | { TokenTree* }

TokenTree       → Token(구분자 제외) | DelimTokenTree

MacroInvocationSemi →
      SimplePath ! ( TokenTree* ) ;
    | SimplePath ! [ TokenTree* ] ;
    | SimplePath ! { TokenTree* }

매크로 호출은 컴파일 타임에 매크로를 확장하고, 그 호출을 매크로의 결과로 치환합니다. 매크로는 다음 상황에서 호출될 수 있어요:

아이템이나 문으로 사용할 때는 MacroInvocationSemi 형식을 쓰는데, 중괄호를 쓰지 않는다면 끝에 세미콜론이 필요합니다. 가시성 한정자(visibility qualifier)는 매크로 호출이나 macro_rules 정의 앞에 절대 올 수 없어요.

#![allow(unused)]
fn main() {
// Used as an expression.
let x = vec![1,2,3];

// Used as a statement.
println!("Hello!");

// Used in a pattern.
macro_rules! pat {
    ($i:ident) => (Some($i))
}

if let pat!(x) = Some(1) {
    assert_eq!(x, 1);
}

// Used in a type.
macro_rules! Tuple {
    { $A:ty, $B:ty } => { ($A, $B) };
}

type N2 = Tuple!(i32, i32);

// Used as an item.
use std::cell::RefCell;
thread_local!(static FOO: RefCell<u32> = RefCell::new(1));

// Used as an associated item.
macro_rules! const_maker {
    ($t:ty, $v:tt) => { const CONST: $t = $v; };
}
trait T {
    const_maker!{i32, 7}
}

// Macro calls within macros.
macro_rules! example {
    () => { println!("Macro call in a macro!") };
}
// Outer macro `example` is expanded, then inner macro `println` is expanded.
example!();
}

위 코드에서 보듯 매크로는 표현식·문·패턴·타입·아이템·연관 아이템 어디서든 호출될 수 있고, 매크로 안에서 다른 매크로를 호출하는 것도 가능해요. example!()은 먼저 바깥 매크로 example이 확장된 뒤, 그 안의 println 매크로가 확장됩니다.

매크로 호출은 두 종류의 스코프로 해석됩니다:

더 알아보기 (Learn more)