rustmacrosrust-macrosrust-2018rust-2021

Can Rust macros be shared across editions?


Say a Rust 2018 macro defines an async function inside it. The syntax it would use would be incompatible with Rust 2015. So if you're compiling your crate with 2015 edition, wouldn't this expanded code from the macro conflict with that?

I'm not that familiar with inner workings of either procedural or declarative macros, but I imagine they need to produce edition-specific code, because their output is going to be treated the same as the rest of the code. If so, how can I share macro exports across edition boundaries. Do I need to re-write them on a per-edition basis? That doesn't seem scalable especially if editions are supposed to be released every 3 years or so.


Solution

  • Yes, macros developed under one edition can be used by code in other editions.

    This was carefully planned as part of the Edition mechanism to prevent ecosystem stagnation. For macros in particular, the Rust Edition Guide explains this in more detail:

    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.

    An example is given for a macro which only works in Edition 2015:

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

    This uses dyn as an identifier, which is illegal in Rust 2018. However, since this macro was written in the 2015 Edition, any code written in this context is parsed and interpreted under the rules of that edition, in isolation from the caller's code. Thanks to this "hygiene" of macros, they can be seamlessly used in Rust 2018, 2021, or any other edition.

    The same thing applies to macros written in more recent editions. Even if a procedural macro declares an async function, there is ultimately a common ground which does not depend on this syntax (MIR).

    The only exception to this, of course, is when migrating existing code between editions.