Creating a string literal inside a macro

I'm trying to exchange a string literal in a macro for one
composed from parts ... with compile-time macros!

probably incidental to the problem but I'm modifying one of the examples from the hot-lib-reloader crate by packaging things in my own macro.

inside my macro the call to a hot-lib function:

hot_functions_from_file!("lib/src/lib.rs")

with which I have tried many permutations such as:

hot_functions_from_file!(
   concat!("lib/src","lib.rs")
)

but always to get the error:

error: expected string literal

Other ways I have tried include format! , const_format, and constcat::concat!.
I have used RUSTFLAGS="-Zmacro-backtrace" cargo build ..
which didn't help.

I 'm totally confused. Aren't these compile-time macros sufficient for creating string literals? Is it their use inside a macro definition screwing things up?

Macros are expanded from the outside in, not from the inside out. This means you can't have macro calls in macros' parameters: the "macro call" is just part of the AST that gets passed in.

The example program, shown below, prints "hello", not "this is unreachable".

macro_rules! outside {
    // This doesn't get called
    ($x:literal) => { $x };
    // This gets called
    (inside!()) => { "hello" };
}

macro_rules! inside {
    () => { "this is unreachable" };
}

fn main() {
    let x = outside!(inside!());
    println!("{x}");
}

Playground link

3 Likes

Wouldn't the example produce "hello world" whichever was inside/outside. after all, whether it's matching against the AST of the call or the literal "..unreachable..", it's still a match?

but I see your point.
does it mean any strings I put together must be done outside the quote!(..) or is there another way of forming them inside the quote! at macro expansion time (the macro I am creating)?
I'm particularly interested in getting an envar which is only going to be valid at expansion time.

... I ask though it looks infeasible!

The point of the example is that macros don't work like functions.

In normal function calls, like outside(inside()), the inside function gets called first. Then, whatever it returns gets passed to outside(...).

Macros don't work like that. When I call outside!, it has a match arm that actually accepts the token tree inside!(). That only works because macros are evaluated from the outside-in.

Many of the macros baked into the language, like env!, actually accept an arbitrary token tree as their parameters and then do the expansion internally. This works:

// This works because the builtin macro actually accepts other macros
// and expands them itself.
macro_rules! inside {
    () => { "PATH" };
}

fn main() {
    let x = env!(inside!());
    println!("{x}"); // prints the contents of PATH
}

But if I wrote a go-between macro that only accepted a literal, it wouldn't work:

// This does not work, because `myenv` doesn't accept arbitrary token trees.
macro_rules! inside {
    () => { "PATH" };
}

macro_rules! myenv {
    ($x:literal) => { env!($x) };
}

fn main() {
    let x = myenv!(inside!()); //~ ERROR no rules expected the token `inside`
    println!("{x}");
}

To work around this, instead of accepting a literal, accept an arbitrary token tree.

// This example works, and prints PATH, because myenv accepts arbitrary token trees
macro_rules! inside {
    () => { "PATH" };
}

macro_rules! myenv {
    ($($x:tt)+) => { env!($($x)+) };
}

fn main() {
    let x = myenv!(inside!());
    println!("{x}");
}
2 Likes

OK, thanks. So a PR against the hot-lib-reloader crate then...

hot_functions_from_file isn't even really a macro. It gets parsed by a proc macro.

1 Like