宏 Macros

在很早的版本中, C 语言就提供了宏 (macro), 但是那里的宏只是简单的宏展开(macro expansion), 进行字符串替换; 宏本身 并没有语法检查, 容易出错, 尤其是处理参数时; 只是在使用时把宏展开, 再由编译器检查. 比如:

#ifndef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif

上面用了非常多的括号, 因为宏可能被用在各种地方, 需要做好参数的保护, 保证代码被展开后依然是正确的.

但 Rust 语言中的宏与语言本身结合得更紧密, 更不容易出错, 它不单单是字符串替换而已.

调用宏与一般的函数调用类似, 比如: debug_assert!(!s.is_empty());.

使用宏的好处:

  • 可以获取宏调用处的位置信息, 比如 file!(), line!(), column()`, 方便定位到代码; 而函数调用就不能直接得到函数调用位置的信息
  • 可以重用代码, 比如标准库中的 num 模块, 为整数类型实现的各种方法, 大都由宏定义提供, 然后为所有整数一并实现, 可以看下面的代码片段:
macro_rules! from_str_radix_int_impl {
    ($($t:ty)*) => {$(
        #[stable(feature = "rust1", since = "1.0.0")]
        impl FromStr for $t {
            type Err = ParseIntError;
            fn from_str(src: &str) -> Result<Self, ParseIntError> {
                from_str_radix(src, 10)
            }
        }
    )*}
}
from_str_radix_int_impl! { isize i8 i16 i32 i64 i128 usize u8 u16 u32 u64 u128 }

macro_rules! impl_helper_for {
    ($($t:ty)*) => ($(impl FromStrRadixHelper for $t {
        const MIN: Self = Self::MIN;
        #[inline]
        fn from_u32(u: u32) -> Self { u as Self }
        #[inline]
        fn checked_mul(&self, other: u32) -> Option<Self> {
            Self::checked_mul(*self, other as Self)
        }
        #[inline]
        fn checked_sub(&self, other: u32) -> Option<Self> {
            Self::checked_sub(*self, other as Self)
        }
        #[inline]
        fn checked_add(&self, other: u32) -> Option<Self> {
            Self::checked_add(*self, other as Self)
        }
    })*)
}
impl_helper_for! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize }

但是, 只应该在必要时才使用宏; 如果考虑代码重用, 可以优先考虑使用泛型, 或者 traits:

  • 宏代码更难阅读, 也就更难维护
  • 宏支持的传入参数很丰富, 也更复杂

本章先介绍一些常用的宏, 随后介绍 Rust 支持的两种宏写法:

  • 声明宏 Declarative Macros
  • 过程宏 Proc Macros

最后, 会介绍一个第三方库, syn, 用于解析过程宏用到的 TokenStream.