0

I have a custom derive macro which generates some code for a struct it is applied to. That code uses few types (bytes::Buf, ect) that need to be imported.

#[derive(MyMacro)]
struct S { ... }

What is the correct way to handle it?

If I leave it with user to add required imports to a file where my macro is used -- every time macro is changed I run a risk of breaking user code (by introducing new import requirement).

If I add required import to the generated/emitted code -- compiler complain about duplicated imports every time macro is used more than once in the same file. Plus, I run the risk of breaking user code if (after I change the macro) I introduce an import user already used.

C.M.
  • 3,071
  • 1
  • 14
  • 33

1 Answers1

2

Disclaimer: This is just my understanding of the best practices. I can not offer links to official guidelines backing up my claims. So take them with a grain of salt.

I think that macros in general should never do relative imports, or rely user provided external structs/imports. Macros should be self-contained.

If you do need imports, put your generated code in its own scope and add a use statement there. If you import inside of macros, always use the fully qualified path. That means, if you want to import HashMap, put a use ::std::collections::HashMap; inside of the macro. Again, make sure this import doesn't leak out.

Note the :: in front of it. That annotates that std is a top level module. Otherwise if the macro is used inside of a user::provided::std module, the std would reference this one. Adding the :: in front of it ensures that it is actually the built-in std module.

Finomnis
  • 18,094
  • 1
  • 20
  • 27
  • What about things like `Result` or `usize`? Do I need to use `::core::result::Result` and `::core::primitive::usize` respectively? If yes -- why not `::std::primitive::usize`? – C.M. Oct 05 '22 at 21:46
  • 1
    If you have the choice between `core` and `std`, I'd argue always choose `core`, because it's compatible with `std` crates, but not the other way round. For `usize`, nothing is required, as it is a primitive datatype, just like `u32`. `Result` could be up for debate. I'm uncertain about this one. – Finomnis Oct 05 '22 at 21:56
  • @C.M. I tend to use absolute paths. It's right that primitives can be shadowed, but it's uncommon to see them so, so it's rare to see absolute paths with them. – Chayim Friedman Oct 05 '22 at 22:49