第3章 宏

宏 — Rust API Guidelines

译文 · 基于 Rust API Guidelines

原文链接: https://rust-lang.github.io/api-guidelines/macros.html

宏

输入语法能让人联想到输出 (C-EVOCATIVE)

Rust 宏几乎允许你构想任意输入语法。目标是通过尽可能镜像已有的 Rust 语法,使输入语法对用户其余代码 保持熟悉且内聚。注意关键字与标点的选择和位置。

一个好的指导原则是:使用与宏输出中将要产生的内容相似的语法,尤其是关键字与标点。

例如,若你的宏用输入中给定的名称声明一个结构体,请在该名称前加上关键字 struct,以向读者表明 正在用给定名称声明一个结构体。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 优先这样...
bitflags! {
    struct S: u32 { /* ... */ }
}

// ...而不是没有关键字...
bitflags! {
    S: u32 { /* ... */ }
}

// ...或某个临时词。
bitflags! {
    flags S: u32 { /* ... */ }
}

另一个例子是分号与逗号。Rust 中常量后跟分号,因此若你的宏声明一串常量,即便语法在其他方面与 Rust 略有不同,它们也很可能应后跟分号。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 普通常量使用分号。
const A: u32 = 0b000001;
const B: u32 = 0b000010;

// 因此优先这样...
bitflags! {
    struct S: u32 {
        const C = 0b000100;
        const D = 0b001000;
    }
}

// ...而不是这样。
bitflags! {
    struct S: u32 {
        const E = 0b010000,
        const F = 0b100000,
    }
}

宏如此多样,这些具体例子未必相关,但请思考如何将相同原则应用到你的情形中。

项宏与属性良好组合 (C-MACRO-ATTR)

产生多个输出项的宏应支持向其中任一项添加属性。一个常见用例是将个别项放在 cfg 之后。

1
2
3
4
5
6
7
8
bitflags! {
    struct Flags: u8 {
        #[cfg(windows)]
        const ControlCenter = 0b001;
        #[cfg(unix)]
        const Terminal = 0b010;
    }
}

输出为结构体或枚举的宏应支持属性,以便输出能与 derive 一起使用。

1
2
3
4
5
6
7
bitflags! {
    #[derive(Default, Serialize)]
    struct Flags: u8 {
        const ControlCenter = 0b001;
        const Terminal = 0b010;
    }
}

项宏在任何允许项的地方都能工作 (C-ANYWHERE)

Rust 允许将项放在模块级,或放在更紧的作用域(如函数)内。项宏应在所有这些地方与普通项同样良好工作。 测试套件应至少包含在模块作用域与函数作用域中对该宏的调用。

1
2
3
4
5
6
7
8
9
#[cfg(test)]
mod tests {
    test_your_macro_in_a!(module);

    #[test]
    fn anywhere() {
        test_your_macro_in_a!(function);
    }
}

作为一个简单例子说明可能出错之处:下面这个宏在模块作用域中工作得很好,但在函数作用域中会失败。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
macro_rules! broken {
    ($m:ident :: $t:ident) => {
        pub struct $t;
        pub mod $m {
            pub use super::$t;
        }
    }
}

broken!(m::T); // 可以,展开为 T 与 m::T

fn g() {
    broken!(m::U); // 无法编译,super::U 指向包含模块而非 g
}

项宏支持可见性说明符 (C-MACRO-VIS)

遵循 Rust 关于宏所产生项可见性的语法。默认私有;若指定了 pub 则为公开。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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>

作为一个简单例子说明可能出错之处:下面这个宏对原始类型与绝对路径工作得很好,但对相对路径会失败。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
macro_rules! broken {
    ($m:ident => $t:ty) => {
        pub mod $m {
            pub struct Wrapper($t);
        }
    }
}

broken!(a => u8); // 可以

broken!(b => ::std::marker::PhantomData<()>); // 可以

struct S;
broken!(c => S); // 无法编译
最后修改 August 21, 2026: 更新 (76fc81a2e)