매크로
입력 구문은 출력을 연상시킵니다 (C-EVOCATIVE)
Rust 매크로를 사용하면 원하는 입력 구문을 사실상 무엇이든 만들어 낼 수 있습니다. 가능한 한 기존 Rust 구문을 따라 하여, 입력 구문이 사용자 코드의 나머지 부분과 친숙하고 일관되도록 하십시오. 키워드와 구두점의 선택 및 배치에 주의하십시오.
좋은 지침은 매크로의 출력에서 생성될 것과 비슷한 구문, 특히 키워드와 구두점을 사용하는 것입니다.
예를 들어 매크로가 입력으로 주어진 특정 이름으로 구조체를 선언한다면, 그 이름 앞에 struct 키워드를 붙여 주어진 이름으로 구조체가 선언된다는 것을 읽는 사람에게 알리십시오.
#![allow(unused)]
fn main() {
// Prefer this...
bitflags! {
struct S: u32 { /* ... */ }
}
// ...over no keyword...
bitflags! {
S: u32 { /* ... */ }
}
// ...or some ad-hoc word.
bitflags! {
flags S: u32 { /* ... */ }
}
}
또 다른 예는 세미콜론과 쉼표입니다. Rust에서 상수 뒤에는 세미콜론이 오므로, 매크로가 일련의 상수를 선언한다면 구문이 다른 부분에서 Rust와 약간 다르더라도 상수 뒤에는 세미콜론을 붙이는 것이 좋습니다.
#![allow(unused)]
fn main() {
// Ordinary constants use semicolons.
const A: u32 = 0b000001;
const B: u32 = 0b000010;
// So prefer this...
bitflags! {
struct S: u32 {
const C = 0b000100;
const D = 0b001000;
}
}
// ...over this.
bitflags! {
struct S: u32 {
const E = 0b010000,
const F = 0b100000,
}
}
}
매크로는 매우 다양하므로 이러한 구체적인 예가 들어맞지 않을 수도 있지만, 같은 원칙을 자신의 상황에 어떻게 적용할지 생각해 보십시오.
아이템 매크로는 속성과 잘 조합됩니다 (C-MACRO-ATTR)
출력 아이템을 둘 이상 생성하는 매크로는 그중 어느 아이템에든 속성을 추가할 수 있도록 지원해야 합니다. 흔한 사용 사례 하나는 개별 아이템을 cfg 뒤에 두는 것입니다.
#![allow(unused)]
fn main() {
bitflags! {
struct Flags: u8 {
#[cfg(windows)]
const ControlCenter = 0b001;
#[cfg(unix)]
const Terminal = 0b010;
}
}
}
구조체나 열거형을 출력으로 생성하는 매크로는 출력에 derive를 사용할 수 있도록 속성을 지원해야 합니다.
#![allow(unused)]
fn main() {
bitflags! {
#[derive(Default, Serialize)]
struct Flags: u8 {
const ControlCenter = 0b001;
const Terminal = 0b010;
}
}
}
아이템 매크로는 아이템이 허용되는 모든 곳에서 동작합니다 (C-ANYWHERE)
Rust에서는 아이템을 모듈 수준에 두거나 함수처럼 더 좁은 스코프 안에 둘 수 있습니다. 아이템 매크로는 이 모든 곳에서 일반 아이템과 똑같이 잘 동작해야 합니다. 테스트 스위트에는 최소한 모듈 스코프와 함수 스코프에서의 매크로 호출이 포함되어야 합니다.
#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
test_your_macro_in_a!(module);
#[test]
fn anywhere() {
test_your_macro_in_a!(function);
}
}
}
문제가 생길 수 있는 경우를 보여 주는 간단한 예로, 이 매크로는 모듈 스코프에서는 잘 동작하지만 함수 스코프에서는 실패합니다.
#![allow(unused)]
fn main() {
macro_rules! broken {
($m:ident :: $t:ident) => {
pub struct $t;
pub mod $m {
pub use super::$t;
}
}
}
broken!(m::T); // okay, expands to T and m::T
fn g() {
broken!(m::U); // fails to compile, super::U refers to the containing module not g
}
}
아이템 매크로는 가시성 지정자를 지원합니다 (C-MACRO-VIS)
매크로가 생성하는 아이템의 가시성은 Rust 구문을 따르십시오. 기본적으로는 비공개이고, pub가 지정되면 공개입니다.
#![allow(unused)]
fn main() {
bitflags! {
struct PrivateFlags: u8 {
const A = 0b0001;
const B = 0b0010;
}
}
bitflags! {
pub struct PublicFlags: u8 {
const C = 0b0100;
const D = 0b1000;
}
}
}
매크로의 타입 조각은 유연하게 쓸 수 있습니다 (C-MACRO-TY)
매크로가 입력에서 $t:ty 같은 타입 프래그먼트를 받는다면, 다음 모두와 함께 사용할 수 있어야 합니다.
- 기본 타입:
u8,&str - 상대 경로:
m::Data - 절대 경로:
::base::Data - 상위 상대 경로:
super::Data - 제네릭:
Vec<String>
문제가 생길 수 있는 간단한 예로, 이 매크로는 기본 타입과 절대 경로에서는 잘 동작하지만 상대 경로에서는 실패합니다.
#![allow(unused)]
fn main() {
macro_rules! broken {
($m:ident => $t:ty) => {
pub mod $m {
pub struct Wrapper($t);
}
}
}
broken!(a => u8); // okay
broken!(b => ::std::marker::PhantomData<()>); // okay
struct S;
broken!(c => S); // fails to compile
}