How to use external crates with our macros in Rust
Disclaimer: This is not a guide about how to use external crates with our macros correctly and especially not about good practices related to that. It is just a simple approach.
Recently I made a simple crate named ferris_print which is a wrapper over the crate ferris_says that gives us the
ferrisprint! macro. The purpose behind ferris_print was to learn how to use macros with Rust. But to my surprise, it was not so clear on how to use an external crate with macros.
What is a macro exactly?
Fundamentally, macros are a way of writing code that writes other code, which is known as metaprogramming.
Macros can be very useful when they are well used. However, I don’t think that ferris_print is the best example of a good use case for a macro but a funny one. I strongly advise taking a look at The Little Book of Rust Macros which an excellent source to learn and understand Rust’s macros.
There is one thing to keep in mind about macros using external crates: the Expansion.
Expansion is a relatively simple affair. At some point after the construction of the AST, but before the compiler begins constructing its semantic understanding of the program, it will expand all macros.
In short, when you write a macro its content will be expanded into the code using it. Here is a simple example.
Why using external crate is not so simple?
First, as we have seen above, a macro is a way of writing code that writes other code. Thus if we want to use the code of someone else, I will need to import it. However, if we understand correctly we will need to specify our external dependency inside our macro. Does it mean that every time someone will use my macro, he will be forced to import all my dependencies explicitly in his code?
In fact, it is possible to work like that but it will be annoying for the users of our macro. Instead, we will use the special variable
$crate . Within a macro imported from a crate named
foo, the special macro variable
$cratewill expand to
::foo. That allows us to reference our internal functions without forcing the user to import them explicitly. Well but what about external dependencies we use?
Once we understand the visibility in Rust, we can figure out the way to do. In order to expose our private dependencies we will need to write this:
pub extern crate other_crate;
By doing this we are now able to use the other_crate inside my macro like this:
The code above calls the function
extern_function() from the other_crate inside our macro. Here is the result once expanded outside our crate:
This is why we need to put
pub before the extern crate definition otherwise Rust would complain about calling a private element of our crate.
I provided a simple solution to use external crates inside our macros. Probably not the best but at least it works :) I’m open to suggestions and improvements if you are more seasoned with macros than me.
I suggest you take a look at ferris_print it’s a good example using this solution.