Does Cargo support cyclic dependencies?

I have 2 components that should live in the same crate, but because one of them is a proc_macro and proc_macro crates can only export proc_macros and nothing else (i.e. no types, no traits, no fns, etc), I have to create a rather unnatural architecture between the components.

(As an aside: the restriction that a proc_macro crate can only export proc_macros really should be lifted. While there are probably technical reasons internal to rustc / cargo for this, from the perspective of a macro author it is arbitrary and annoying incidental complexity.)

So the next idea is to make the 2 crates dependent on one another.
However, I get this nagging little feeling that Cargo only deals in tree-shaped dependency graphs and thus won't allow cyclic dependencies between crates.

Does anyone have any concrete info on this?

I guess I just answered my own question: My assumption was correct, Cargo does not support cyclic dependencies.

I'd say the reason is much more fundamental: macros and usual code live in a completely different compilation worlds. This is especially true while cross compiling: proc macros and the end code are compiled for different architectures.

My assumption was correct, Cargo does not support cyclic dependencies.

It's not Cargo, it's the language. Crates (in rust-reference sense of word, not cargo packages) form a direct acyclic graph, and that's not a limitation, that's the main benefit of the idea. Crates allow one to express the idea that there's no cyclic dependency between components.

What's even more fun, Cargo packages actually can have cyclic dependencies. (a package can have an indirect dev-dependency on itself)

True, however it would be nice if a macro author could decide to abstract over that fact. Think of it as syntactic sugar, except on the project level rather than the syntactic level (which I guess would make the term "project sugar" more appropriate).

Generally speaking I agree, cyclic dependencies are a pain to work with from an external viewpoint. The idea was that the cyclic dependency would be a workaround for the proc-macro crate limitations w.r.t. exposing a public API.

As things stand, I solved it by using a Cargo workspace that consists of 3 subprojects:

  1. A crate that does nothing other than define a macro_rules auxiliary macro, let's call it frob!. This is because of another limitation of proc_macros: at present, they cannot expand to expressions (the core issue there is proc_macro hygiene apparently).
  2. A crate containing the proc_macro, let's call it generate!. The proc_macro generates a whole bunch of code.
  3. A facade crate that publicly exports both frob! and generate! to consumers.

This structure is far from simple for what it is and does, and it works only because macro_rules macros can have expansions that refer to code that doesn't even exist in the same crate.

Basically what I'd ideally want is to have the ability to:

  1. Make proc_macros expand to at least expressions
  2. Allow proc_macros to define macro_rule macro definitions (at present generating macro call sites already works)
  3. Allow proc_macro crates to export an API that consists of more than just proc_macro's.

That certainly kills the cyclic dependency idea...

... and I don't think that this changes that fact, but I could be wrong.

1 Like

Proc macros are just a few steps away form working on raw text. If you want to use a something form the main crate you just write the full name of the item in the output.

This will break if the user renames the main crate, but renaming crates is rare so it's not a problem in practice.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.