Rust 过程宏中的 Span

55 阅读2分钟

Rust 过程宏中的 Span

在 Rust 的过程宏(proc-macro)中,Span 是一个非常重要的概念,它表示源代码中某个标记(token)的位置信息。

什么是 Span

Spanproc_macroproc_macro2 模块中的一个类型,它代表:

  • 源代码中某个标记的位置范围
  • 包括起始和结束位置
  • 可以追踪错误发生的位置
  • 在宏展开后保持正确的错误报告位置

Span 的主要用途

  1. 错误报告:当宏检测到错误时,可以使用正确的 span 来报告错误位置
  2. 代码生成:生成代码时可以保留原始标记的位置信息
  3. 语法糖实现:在实现语法糖时保持正确的源代码位置

使用 Span 的常见方式

获取 Span

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Ident};

#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream {
    let ident = parse_macro_input!(input as Ident);
    let span = ident.span(); // 获取标识符的span
    
    // ...
}

创建带 Span 的标记

use proc_macro2::{Ident, Span};
use quote::quote;

let my_ident = Ident::new("my_var", Span::call_site());

合并 Span

let combined_span = span1.join(span2).unwrap_or(span1);

Span 的类型

在过程宏中,有两个主要的 Span 类型:

  1. proc_macro::Span - 标准库中的类型,只能在过程宏中使用
  2. proc_macro2::Span - 来自 proc-macro2 crate,可以在更多上下文中使用

常见的 Span 方法

  • span.start() - 获取起始位置
  • span.end() - 获取结束位置
  • span.source_text() - 获取该 span 覆盖的源代码文本
  • span.join() - 合并两个 span
  • span.resolved_at() - 在指定位置解析的 span
  • span.source() - 获取源代码文件信息

示例:在错误报告中使用 Span

use syn::{Error, Ident};

fn check_ident(ident: &Ident) -> Result<(), Error> {
    if ident.to_string().starts_with('_') {
        Err(Error::new(ident.span(), "标识符不能以下划线开头"))
    } else {
        Ok(())
    }
}

注意事项

  1. Span 信息只有在 nightly Rust 中或设置了特定特性时才能获取完整信息
  2. 某些 Span 方法可能返回近似值而非精确位置
  3. 在测试环境中,Span 可能是模拟的(dummy span)

正确使用 Span 可以大大提高宏的错误报告质量,使开发者更容易定位问题。