Import in proc-macro with quote! using relative path

Hi everyone,

I want to use relative path for imports in my proc-macro sub-crate, but instead the compiler search for imports in root crate. This is how my imports looks like:

pub struct Imports {
    pub byteorder_be: TokenStream2,
    // ...
    pub json_formatter: TokenStream2,
}

impl Imports {
    pub fn get() -> Self {
        Self {
            byteorder_be: quote!(byteorder::BigEndian),
            // ...
            json_formatter: quote!(crate::formatters::JsonFormatter),
        }
    }
}

and this is how my file structure looks like:
image

I want to import JsonFormatter from ::macros::packet::src::formatters.

Could somebody explain if it possible and if yes, how to do this ?

P.S. forgot to say, this is an error on compile:

error[E0433]: failed to resolve: could not find `formatters` in the crate root

You can't just span a module across crate boundaries and a sub-crate is not a thing in a Cargo project. The closest thing to a sub-crate would be a workspace member. Your packet is a whole Cargo package, with manifest file and all. You have to add it in your other package in the [dependencies] section of your manifest (see the Cargo book) and then use it like any other external dependency, i.e. use packet::whatever.

Secondly, you stated that packet is a proc-macro crate. You can only export procedural macros from it, nothing else. Imports is a struct and not a procedural macro, so you can't export it. You could create a non-proc-macro crate that can export it normally though and use that in packet and your other crate.

3 Likes

Excuse me, if I didn't explain it clearly. I mean I want to do the import within proc-macro crate, so in the example above JsonFormatter will be used only for proc-macro where this formatter is defined. In other words, JsonFormatter is for internal usage only and will not be used outside of proc-macro crate.

I still need workspaces for this case ?

JsonFormatter lives in packet::formatters? And you want to import it somewhere else in packet but you can't because of the error? In that case I suspect you forgot to add a pub(crate) mod formatters; in your packet/src/lib.rs file.

yep, all you said is correct. I want to use the import in packet/src/types.rs:

pub struct Imports {
    // ...
    pub json_formatter: TokenStream2,
}

impl Imports {
    pub fn get() -> Self {
        Self {
            // ...
            json_formatter: quote!(crate::formatters::JsonFormatter),
        }
    }
}

this Imports struct will be used in packet/src/lib.rs. I added pub(crate) mod formatters; to the packet/src/lib.rs as you suggested, but error still there:

error[E0433]: failed to resolve: could not find `formatters` in the crate root

Am I using import syntax correctly ?

json_formatter: quote!(crate::formatters::JsonFormatter),

Ah, well, the syntax isn't the problem, it's that you try to put crate::formatters::JsonFormatter in a quote! that you expand in another crate. I highly recommend you read the little book of rust macros to get a better picture of how procedural macros work.

Let's say you have a function-like procedural macro packet::json_formatter that returns quote!(crate::formatters::JsonFormatter). If I call this in my crate, like:

fn main() {
    packet::json_formatter!();
}

the macro would expand to

fn main() {
    crate::formatters::JsonFormatter;
}

But my crate doesn't have a formatters module with a JsonFormatter type in it! That exists only in your packet crate, not mine.

How you normally solve this would be to have a packet crate with formatters::JsonFormatter in it and a packet_derive crate with the json_formatter macro[1] and you would return a quote!(::packet::formatters::JsonFormatter) from json_formatter. Then in my crate I would import both packet and packet_derive and the expansion of packet_derive::json_formatter!(); would be

fn main() {
    ::packet::formatters::JsonFormatter;
}

which would be a valid path, because I have the packet crate in my dependencies. serde with serde_derive works like that, for example.

What helped me understand macros better is that I thought of quote! returning nothing but a string that happens to contain valid Rust code. I return this string that gets embedded/expanded in some other place. The code I returned must make sense in the context of the place it got expanded in, not inside the context of my macro.


  1. While you can't export anything but procedural macros from a proc-macro crate, you can re-export the macros from packet_derive from packet, for convenience. ↩ī¸Ž

2 Likes

Thank you for the detailed explanation !

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.