4

I have a proc-macro that also exposes some types, so I'm using the following crate structure:

  • foo_core exports FooTrait
  • foo_macro (depends on foo_core) exports foo_macro, which generates some structs that implement FooTrait
  • foo (depends on foo_core and foo_macro) re-exports FooTrait and foo_macro

The problem I'm running into is that re-exporting foo_core changes it's path.

foo_macro generates code that looks roughly like:

struct Bar;

impl ::foo_core::FooTrait for Bar {
  // ...
}

I have a bunch of trybuild tests in foo_macro which pass, since foo_core is an available crate.

The problem is when I try to write tests in foo (or in crates that depend on foo only), I get the error telling me it can't find the crate foo_core.

This makes some sense to me; the consumer of foo hasn't explicitly depended on foo_core, but I'd like it if users of my crate didn't have to add foo and foo_core (and make sure the versions match), instead I'd like them to be able to add just foo and it would work.

Currently, I can access foo_core via foo::foo_core::FooTrait, but obviously this is different to how my macro generates the code (::foo_core::FooTrait). Is there a way to make foo re-export the entire foo_core crate?

If that's not possible, what's the best way of going about this pattern? Can I somehow make foo_macro change its behaviour depending on what crate it's defined in? Or should I just move all my tests to foo instead of foo_macro?

Is there a way to have a public trait in a proc-macro crate? An answer to this question mentions this problem, but simply says "you must use the fully-qualified names", but my understanding was that this is what the leading :: on the path means.

This seems like a relatively common pattern, so I'm hoping there's a nice solution out there.

cameron1024
  • 9,083
  • 2
  • 16
  • 36
  • 1
    It seems to be similar situation like three crates in Rocket (rocket_codegen, rocket_http, and rocket ) https://github.com/SergioBenitez/Rocket/tree/master/core. So it seems that the lib `foo` in your case will re-export `foo_core` like rocket rexports `rocket_http` in `rocket` https://github.com/SergioBenitez/Rocket/blob/v0.5-rc/core/lib/src/lib.rs#L129-L141. And so in your case your fully qualified names in generated and tested code would be `foo::foo_core::FooTrait` ? – bits Dec 23 '21 at 06:33

1 Answers1

4

In your foo crate, you can completely reexport foo_core with either:

  • pub extern crate foo_core;
  • pub use foo_core;

With either of those statements, all of foo_core can be accessed through foo::foo_core:: when only having foo in the [dependencies] section of your Cargo.toml. Your proc macro would emit ::foo::foo_core:: paths then. This of course requires that users don't use foo_macro and foo_core directly. But this is common: usually, people are not supposed to use the macro crate directly, but only the "top level crate".

This is a common pattern, not only with your own crates. For example, in one of my libraries, my proc macro generates call to serde functions (not my library). So I pub use serde; in my crate so that users of my library don't have to depend on serde directly.

Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305