Changes in Rust macro re-exporting

The changes in recent Rust

This commit to the Rust ecosystem removed the previous macro re-export functionality which is now subsumed by a cleaner mechanism which is also going to be stabilised in the end, this is tracked by this issue.

Unfortunately the previous re-export mechanism was used quite a lot in the embedded field, causing quite a few crates to break when compiled with rustc versions from the beginning of May 2018, which is especially frustrating when pulling in broken dependencies since those can not be easily (and permanently) addresses locally. While some HAL implementations, Board support crates and drivers may also be affected, the most significate breakage can be found in every peripheral register crate created by the popular svd2rust tool.

This post is trying to explain which changes are required to fix broken crates and how to temporarily work around problems with foreign crates.

The signs of the problem

Spotting the issue is rather simple. Compile some code and if the compiler barfs, then either the code itself or one of its pulled in dependencies are affected:

cargo build --release --examples
    Updating git repository `https://github.com/japaric/stm32f103xx-hal`
    Updating registry `https://github.com/rust-lang/crates.io-index`
   Compiling num-traits v0.2.0
   Compiling semver-parser v0.7.0
   Compiling libc v0.2.36
   Compiling cortex-m v0.3.1
   Compiling vcell v0.1.0
   Compiling cortex-m-rt v0.4.0
   Compiling bare-metal v0.1.1
   Compiling aligned v0.1.1
   Compiling cortex-m v0.4.3
   Compiling r0 v0.2.2
   Compiling untagged-option v0.1.1
   Compiling stm32f103xx-hal v0.1.0 (https://github.com/japaric/stm32f103xx-hal#330c9044)
   Compiling nb v0.1.1
   Compiling cast v0.2.2
   Compiling numtoa v0.0.7
   Compiling static-ref v0.2.1
   Compiling volatile-register v0.2.0
   Compiling embedded-hal v0.1.2
   Compiling time v0.1.39
   Compiling semver v0.9.0
   Compiling stm32f103xx v0.9.1
   Compiling num-integer v0.1.36
   Compiling num-iter v0.1.35
   Compiling num v0.1.42
   Compiling chrono v0.4.0
   Compiling rustc_version v0.2.2
   Compiling cortex-m-rt v0.3.13
   Compiling stm32f103xx v0.8.0
error[E0557]: feature has been removed
 --> /Users/egger/.cargo/registry/src/github.com-1ecc6299db9ec823/stm32f103xx-0.8.0/src/lib.rs:1:106
  |
1 | # ! [ cfg_attr ( feature = "rt" , feature ( global_asm ) ) ] # ! [ cfg_attr ( feature = "rt" , feature ( macro_reexport ) ) ] # ! [ cfg_attr ( feature = "rt" , feature ( used ) ) ] # ! [ doc = "Peripheral access API for STM32F103XX microcontrollers (generated using svd2rust v0.12.0)\n\nYou can find an overview of the API [here].\n\n[here]: https://docs.rs/svd2rust/0.12.0/svd2rust/#peripheral-api" ] # ! [ allow ( private_no_mangle_statics ) ] # ! [ deny ( missing_docs ) ] # ! [ deny ( warnings ) ] # ! [ allow ( non_camel_case_types ) ] # ! [ feature ( const_fn ) ] # ! [ no_std ]
  |                                                                                                          ^^^^^^^^^^^^^^
  |
note: subsumed by `#![feature(use_extern_macros)]` and `pub use`
 --> /Users/egger/.cargo/registry/src/github.com-1ecc6299db9ec823/stm32f103xx-0.8.0/src/lib.rs:1:106
  |
1 | # ! [ cfg_attr ( feature = "rt" , feature ( global_asm ) ) ] # ! [ cfg_attr ( feature = "rt" , feature ( macro_reexport ) ) ] # ! [ cfg_attr ( feature = "rt" , feature ( used ) ) ] # ! [ doc = "Peripheral access API for STM32F103XX microcontrollers (generated using svd2rust v0.12.0)\n\nYou can find an overview of the API [here].\n\n[here]: https://docs.rs/svd2rust/0.12.0/svd2rust/#peripheral-api" ] # ! [ allow ( private_no_mangle_statics ) ] # ! [ deny ( missing_docs ) ] # ! [ deny ( warnings ) ] # ! [ allow ( non_camel_case_types ) ] # ! [ feature ( const_fn ) ] # ! [ no_std ]
  |                                                                                                          ^^^^^^^^^^^^^^

error[E0658]: The attribute `macro_reexport` is currently unknown to the compiler and may have meaning added to it in the future (see issue #29642)
 --> /Users/egger/.cargo/registry/src/github.com-1ecc6299db9ec823/stm32f103xx-0.8.0/src/lib.rs:4:1
  |
4 | #[macro_reexport(default_handler, exception)]
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = help: add #![feature(custom_attribute)] to the crate attributes to enable

error: aborting due to 2 previous errors

Some errors occurred: E0557, E0658.
For more information about an error, try `rustc --explain E0557`.
error: Could not compile `stm32f103xx`.
warning: build failed, waiting for other jobs to finish...
error: build failed

As can be easily seen the compiler will make a good effort showing what didn’t work and (at least partially) also shows the fix. But as can also be seen, the breakage occurs in a pulled in dependency which requires an upstream fix and can at best be worked around temporarily by messing with the cached data until the maintainer addresses the problem and uploads a fixed version.

How to address the issue?

For peripheral register crates using svd2rust

Peripheral register crates can be easily addressed by simply using a svd2rust version 0.12.1 or later. This will change from the use of the macro_reexport feature to the use_extern_macros feature which is eventually going to be stabilised. However, the maintainer japaric already announced in this comment that re-exports of macros are going to be purged from svd2rust with version 0.13.0 altogether.

For all other crates (e.g. HAL implementation, BSP or driver)

If you’re using re-exported macros in your crate, you will need to change the code to use the new use_extern_macros feature as indicated by the error output.

If your code read something like:

#![cfg_attr(feature = "rt", feature(macro_reexport))]

#[macro_reexport(block)]
pub extern crate nb;

then the new version would typically read:

#![cfg_attr(feature = "rt", feature(use_extern_macros))]

#[macro_use(block)]
pub extern crate nb;
pub use nb::block;

This will make the block macro available both for local use in your crate and for crates depending on your crate.

Temporary workaround for cached upstream crates

As you may have noticed from my initial example, cargo will fetch the crate only once and then rely on the local cache on disk to provide the source code to the dependant crate and happily print the local path on the filesystem:

...
error[E0557]: feature has been removed
 --> /Users/egger/.cargo/registry/src/github.com-1ecc6299db9ec823/stm32f103xx-0.8.0/src/lib.rs:1:106
...

Hence you can easily modify the local copy to contain the change mentioned above and your code will happily compile again (unless there’s another depedency with the same issue) as long as the dependency doesn’t change to a different version.

Please make sure to notify the maintainer of such a crate about the issue so it can fixed upstream.

Get rid of re-exports altogether

As japaric suggested one could also simply get rid of all macro re-exports and always “use” directly from the original crate which results in a loss of comfort and may break dependent crates if you offered re-exported macros in the past but is guaranteed to be and stay stable for all times. Otherwise if all hell breaks lose and the this feature fails to stabilize until 1.28, such a crate might be excluded from being used with stable Rust.

In conclusion

I hope this post was a bit helpful if you were as surprised as I was about this sudden and thorough breakage of major parts of the embedded Rust ecosystem. Let’s make sure we get all the fallout addressed as quickly as possible to continue with the regular development program.