Nesting two proc macro calls, like cstr!(indoc!(""))

Hello,

I'm the author of crate color-print, and I got an interesting issue about nesting color_print::cstr!() with indoc::indoc!().

To my actual understanding about Rust proc macros, it's not possible to do that, because the indoc crate is not accessible from the companion crate color-print-proc-macro : even if I would test for a match between indoc!(...) and the token stream, I could not "call" indoc!() because it's not accessible from the proc macro crate. But maybe I'm missing something?

My main questions are:

  1. Is there a solution to solve this kind of problem?
  2. (Crate design question) Otherwise, do you consider as a good choice to add a indoc feature as explained below in my anwser (see " Possible solution for a future version" below)?

Any help is welcome:) Please answer with simple english.

I copy/pasted below the OP's issue, and my initial anwser (extract):

Copy/paste of the issue:

I was hoping to use this crate together with clap as per their documentation.

But my help messages are huge and I'm already using indoc crate to wrap them.

Unfortunately it seems that two crates don't combine:

# Error: Format string must be a string literal
let s = color_print::cstr!(indoc::indoc!(
    r#"
    My message
    "#
));

Extract of my first response:

The problem...

Actually, cstr!() macro is implemented as a proc macro, and as far as i know, nesting proc macro calls is not possible, because the indoc crate is an external crate, not accessible from the color-print-proc-macro crate.

I may be wrong about this (I will take a look on it a bit deeper); but I'm quite confident about it because indoc!() also raises an error when calling indoc!(cstr!("")) (with a more readable error message).

Possible solution for a future version

indoc is a very popular crate, and using it in with cstr!() could be useful for other people; so maybe a indoc feature could be added to color-print, which would allow the following special syntax: cstr!(indoc!("..."))...

This solution is far from perfect, but it's not really hard to implement and could solve this kind of issue at least for indoc.

What you want is eager expansion which, last I checked, is still not a thing you can do unless you're the compiler. It's not a "can't see the other macro to call it" issue, it's a "you can't do anything but lazy expansion" problem.

The workaround I've used in the past is to support callbacks/cps. So, indoc could support something like this:

indoc!("    An example" => cstr!())

The output from indoc! would then be:

cstr!("An example")

In other words, indoc calculates its output, then injects it into the macro call specified on the end. I typically just appended it to the end of the callback macro's arguments (i.e. => cstr!(some, tokens,) would turn into cstr!(some, tokens, "An example") on output). You could also be more fancy and support more complete lambda-style syntax with an explicit argument name, but that's probably overkill.

The user has to write the order of macros "inside out", but it allows the outer macro to pass information to the inner macro.

Edit: Of course, the downside to this is that unless indoc is willing to add this functionality, it doesn't really help your users. Short of directly supporting indoc in your crate with special syntax, nothing else comes to mind.

3 Likes

It is indeed impossible to eagerly expand macros. Unlike values, which compose inner to outer, macros compose outer to inner.

There's a fairly straightforward option for color_print::cstr!, though: instead of taking just one string literal, visit all tokens in the input and process all string tokens.

There are tricks possible to actually pipe macros together ("macro continuations") but they all require the nested macro to provide the functionality. (In short, instead of expanding to %EXPANDED%, the macro expands to your_macro!{%EXPANDED%}.)

1 Like

It is a nightly feature currently.

2 Likes

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.