案例

assert_sync!

这是一个基础案例1,目的是在编译期间2测试某个类型是否实现了 Sync

Sync trait 表示该类型能在不同的线程之间安全地共享引用。当编译器确定某类型合适的话,那么会自动实现 Sync

这里基于最基础的语法:声明泛型结构体来实现编译期断言。

// 只需声明一个 Unit 结构体,附加 where 语句即可
// 技巧:在没有使用泛型参数的情况下也可以使用 where 语句
struct A where SomeType: SomeTrait;

利用声明宏和过程宏,完成目的:

// 声明宏
macro_rules! assert_sync_dcl {
    ($t:ty) => {{
        struct _AssertSync where $t: Sync;
    }};
}

// 过程宏
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, quote_spanned};

#[proc_macro]
pub fn assert_sync_proc(t: TokenStream) -> TokenStream {
    let ty = TokenStream2::from(t);
    TokenStream::from(quote! {{struct _AssertSync where #ty: Sync;}})
}

// 除了 Span 之外,与 `assert_sync_proc` 等价(但添加了一些调试打印)
#[proc_macro]
pub fn assert_sync_proc_spanned(t: TokenStream) -> TokenStream {
    use syn::spanned::Spanned;
    let ty = TokenStream2::from(t);
    let assert_sync = quote_spanned! {ty.span()=>
        {struct _AssertSync where #ty: Sync;}
    };
    // dbg!(&ty);
    // println!("{}", assert_sync);
    TokenStream::from(assert_sync)
}

// 使用断言
fn main() {
    assert_sync_proc!(Vec<u8>);
    // assert_sync_proc!(std::rc::Rc<u8>);

    assert_sync_proc_spanned!(Vec<u8>);
    // assert_sync_proc_spanned!(std::rc::Rc<u8>);

    assert_sync_dcl!(Vec<u8>);
    // assert_sync_dcl!(std::rc::Rc<u8>);
}

取消注释的那部分,会在编译时看到以下一条错误信息(过程宏错误信息可点击右上角取消隐藏看到):

// 声明宏错误信息
error[E0277]: `Rc<u8>` cannot be shared between threads safely
  --> src/main.rs:5:9
   |
5  | /         struct _AssertSync
6  | |             where $t: Sync;
   | |___________________________^ `Rc<u8>` cannot be shared between threads safely
...
24 |       assert_sync_dcl!(std::rc::Rc<u8>);
   |       --------------------------------- in this macro invocation
   |
   = help: the trait `Sync` is not implemented for `Rc<u8>`
   = help: see issue #48214
   = help: add `#![feature(trivial_bounds)]` to the crate attributes to enable
   = note: this error originates in the macro `assert_sync_dcl` (in Nightly builds, run with -Z macro-backtrace for
more info)

// 过程宏错误信息:不指定 Span
error[E0277]: `Rc<u8>` cannot be shared between threads safely
 --> src/main.rs:16:5
  |
16 |     assert_sync_proc!(std::rc::Rc<u8>);
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Rc<u8>` cannot be shared between threads safely
  |
  = help: the trait `Sync` is not implemented for `Rc<u8>`
  = help: see issue #48214
  = help: add `#![feature(trivial_bounds)]` to the crate attributes to enable
  = note: this error originates in the macro `assert_sync_proc` (in Nightly builds, run with -Z macro-backtrace for
more info)

// 过程宏错误信息:指定 Span 可以更清楚地知道错误区域
error[E0277]: `Rc<u8>` cannot be shared between threads safely
 --> src/main.rs:20:31
  |
20 |     assert_sync_proc_spanned!(std::rc::Rc<u8>);
  |                               ^^^^^^^^^^^^^^^ `Rc<u8>` cannot be shared between threads safely
  |
  = help: the trait `Sync` is not implemented for `Rc<u8>`
  = help: see issue #48214
  = help: add `#![feature(trivial_bounds)]` to the crate attributes to enable

声明宏的错误信息显然能够定位到声明宏所定义的地方,但过程宏的错误信息只定位到它被使用的地方,而且 Span 范围越小,就越清晰地指明错误的关键。

两种方法都能把主要的错误准确报告出来:Rc<u8> cannot be shared between threads safely

1

该例子受 quote::quote_spanned! 文档的启发。

2

在编译时做出断言是这个例子的另一大亮点,利用类似的技巧,可以做出很多静态断言,参考 static_assertions crate。