Rust 宏可以跨版本共享吗?

Can Rust macros be shared across editions?

假设一个 Rust 2018 宏在其中定义了一个 async 函数。它将使用的语法与 Rust 2015 不兼容。因此,如果您使用 2015 版编译您的 crate,宏中的扩展代码不会与之冲突吗?

我不太熟悉过程宏或声明宏的内部工作原理,但我想他们需要生成特定于版本的代码,因为他们的输出将与其余代码一样对待。如果是这样,我如何跨版本边界共享宏导出。我需要在每个版本的基础上重写它们吗?这似乎无法扩展,尤其是如果版本应该每 3 年左右发布一次。

是的,在一个版本下开发的宏可以被其他版本的代码使用。

这是作为版本机制的一部分精心策划的,以防止生态系统停滞。特别是对于宏,Rust Edition Guide explains this 更详细:

Macros use a system called "edition hygiene" where the tokens within a macro are marked with which edition they come from. This allows external macros to be called from crates of varying editions without needing to worry about which edition it is called from.

仅适用于 2015 版的宏示例:

#[macro_export]
macro_rules! foo {
    () => {
        let dyn = 1;
        println!("it is {}", dyn);
    };
}

这使用 dyn 作为标识符,这在 Rust 2018 中是非法的。但是,由于该宏是在 2015 版中编写的,因此在此上下文中编写的任何代码都将根据该规则进行解析和解释版本,与调用者的代码隔离。由于宏的这种“卫生”,它们可以在 Rust 2018、2021 或任何其他版本中无缝使用。

同样的事情也适用于 更新版本 中编写的宏。即使过程宏声明了一个 async 函数,最终还是有一个不依赖于此语法的共同点 (MIR)。

当然,唯一的例外是 migrating existing code between editions

rust 版本的规则之一是 crate 应该可以互操作,即使它们不是来自同一版本。 (source)

由于版本卫生 属性,此规则也适用于宏。每个宏 span 都标有它们的版本 (source code),当宏展开时,解析步骤是在其声明版本中进行的,而不是在版本调用站点中进行的。

在较低级别的表示中,rust 版本之间的差异不再存在,因此所有内容都可以编译并且 运行 (source)

然而,当定义了宏但未在迁移的板条箱中使用宏时,可能会出现宏的迁移问题。在这种情况下,cargo fix --edition 将无法正常工作。