Using absolute paths
A derive macro generates code that gets pasted into someone else’s crate, in a module you’ve never seen. You don’t control what’s in scope there — and that’s a problem, because the code you generate has to compile no matter what names the user has brought into scope.
The trap
Suppose your macro generates code that returns a String:
#![allow(unused)]
fn main() {
quote! {
impl #name {
pub fn type_name(&self) -> String {
String::from(stringify!(#name))
}
}
}
}
This looks fine, and it works in every test you write. Then a user does this:
#![allow(unused)]
fn main() {
type String = MyFancyString;
#[derive(TypeName)]
struct Widget;
}
Now the String in your generated code resolves to MyFancyString, and their build breaks
with a confusing error pointing at their #[derive(TypeName)], not at your macro. Derive
macro output is not path-hygienic: bare names like String, Option, Result, Vec, and
Box resolve at the call site, against whatever the user has in scope.
The fix: absolute paths
Refer to every external item by a fully-qualified, absolute path — one starting with a leading
::. A leading :: anchors the path at a crate root, so it can’t be intercepted by a local
name:
#![allow(unused)]
fn main() {
quote! {
impl #name {
pub fn type_name(&self) -> ::std::string::String {
::std::string::String::from(stringify!(#name))
}
}
}
}
Common standard-library paths:
| Bare name | Absolute path |
|---|---|
Option, Some, None | ::core::option::Option, ::core::option::Option::Some, … |
Result, Ok, Err | ::core::result::Result, … |
String | ::std::string::String |
Vec | ::std::vec::Vec |
Box | ::std::boxed::Box |
Default::default() | ::core::default::Default::default() |
Many
coreitems are also re-exported fromstd, so::std::option::Optionworks too. Using::corelets your generated code work in#![no_std]crates as well — a nice habit if you want your macro to be maximally portable.
Referring to your own crate
The same problem applies to your crate’s items. If your macro lives in a crate my_macros but
the trait it implements lives in a companion crate my_lib, the generated code must name
::my_lib::TheTrait, not TheTrait — the user may not have imported it. This is the other half
of why real macro crates use the re-export pattern you saw in chapter 3.
Robust crates often go one step further and accept the crate path as an attribute (serde’s
#[serde(crate = "...")]) so the macro keeps working even if the user renamed the dependency. That’s beyond this exercise, but it’s the same idea taken to its conclusion.
To keep each later exercise focused on its own new idea, the generated code in this chapter
mostly sticks to bare names (String, Vec, format!). That’s fine for tests that don’t shadow
those names — but in a macro you ship, qualify every external item with an absolute path, as the
capstone in the last section does.
Exercise
The exercise for this section is located in 04_derive/03_absolute_paths
The TypeName derive macro generates a type_name() method, but it refers to String by its
bare name. examples/shadowed.rs shadows String and currently fails to compile. Fix the
macro to use absolute paths so both passing tests compile.