Is there a way let macros at different place communicate?

I want to embed a tiny programming language in Rust by using procedural macro, but a macro call cannot communicate with others at different call places, so variables can't be shared. Any solutions? Thanks.

For instance:

fn main() {
    call! {
        let a = 10;
    }
    call! {
        let b = a; // How to let it knows `a` as `a` is not a Rust variable?
    }
}

macros are hygienic, identifiers (e.g. a variable name) from one macro is never the same as identifiers from another macro. if you need this, the identifier tokens must be created outside the macro and passed as arguments to the macros. example:

macro_rules! foo {
    ($var:ident) => {
        let $var = 42;
    }
}

macro_rules! bar {
    ($var:ident) => {
        println!("{}", $var);
    }
}

fn main() {
    foo!(x);
    bar!(x);
}

Thanks but I'm not saying macro_rules, if I understand correctly the procedural macro is defined by a divided crate.
I want these macros to communicate not through rust's features, like local TCP servers etc.

well, if you are talking about procedural macros, they are not hygienic at all, theyjust transform syntactical tokens.

I think I don't understand your question well. from your example code, I thought you just want to create a variable in one macro invocation, then use the variable in another. procedural macros can do this without any problem.

but apparently, you have something different in mind. what do you mean by "communicating"? please explain your intention in detail, or show a concrete example, so we can understand your problem better.

1 Like

This is technically possible, since procedural macros are normal Rust code and can use anything, but highly discouraged and unreliable. Macros are expected to be pure and not do anything other then shuffle tokens around; if they have any state (internal or external), they will often break due to incremental compilation, and if they're using some external resource, this can (and probably will) be treated as potential security hazard.

4 Likes

What is the output you want your macro generation to produce in this scenario? I'm asking about both the generated Rust output and any other outputs (including stdout/stderr messages) you would want to see.

Macros cannot communicate. They run conceptually, if not in practice, in isolated environments, in parallel, with no fixed order. Macros communicating with macros is neither possible nor desirable. Technically macros are not (currently) sandboxed, and so they can use any OS-level standard primitives for communication. In practice, since they can expand in arbitrary order, you can't really make it work. Also, macro expansion must be idempotent and side-effect free, since the same macro can be expanded multiple times for any reason (e.g. a user in an IDE can just expand your macro to view its output, without touching any other macros).

Proc macros are not hygienic, but unless you want your code to break in wonderful and exciting ways, you really should just be enforcing hygiene on your own rather than abusing its non-existence. This means if you need a shared identifier, you really should just pass its name to the macros explicitly, just like you would do with macro_rules!.

2 Likes

@Cerber-Ursi @afetisov Thanks for your answer!

Now I understand that: procedural macros do have the ability to use any tools outside Rust, but since proc macro may be execute an uncontrollable number of times in uncontrollable places and time, so the expanded content will become unpredictable if using side-effects. So the conclusion is it's able but better not do that.

But I have another question: As we have macros like env! and file! in std that can read controllable outside environments, do we have this possibility: we put a file (a JSON file, for example) in a crate, then use a macro to "link" it, so that crates dependent on this parent crate can see the "linked" (JSON) file's content at compile time.

How is this different from parent crate simply include_str!ing this file and then making the result pub?

The macro in the dependent crate doesn't see the string's content, all it can see is there is a constant whose type is string until at runtime.

I don't think I understand what you're trying to do. Indeed, why doesn't include_str! solve your issue? Macros can't do compile-time introspection, so unless you explicitly pass that string item into a macro, no one can look at its value. And even if some macro would do it, why is it an issue?

To make some optimization. I understand that this need is rather niche, but it would be great if it could be done. For example, computing the effect of multiple CSS code segments on an element at compile time. It's hard to solve this problem using constant computation.

1 Like

You absolutely can make a proc-macro parse a JSON file at compile time, but the path to that file must be given to the macro as a string literal.

1 Like

Yes, but we're able to export neither the content nor the path of the files so that dependent crates could access them.

What do you mean by that? Why couldn't you?

in crate A:

// `my_macro` can read the file's content, it's OK
const FILE: &str = my_macro!("path/to/file.json");

in crate B that dependents A:

// `my_macro2` can't see the file's content
my_macro2!(FILE);

And the solution may be my_macro! expands to a macro rule definition (_private for example), call which expands to my_macro2!

// `my_macro`'s expansion
macro_rules! _private {
    () => {
       #[macro_export]
        my_macro2!(r#"{"file": "content"}"#)
    }
}

when we call _private! in crate B, we indirectly call my_macro2!, only in this way my_macro2! could see the file's content.


I wonder whether put file content in the environment variable would work and how to? Does rustc reserve environment variables during incremental compilation?

There is Inventory which let's you do magical things. But it works via linker magic, hence is not portable (e.g. fails on Web/Wasm).
This is used in TypeTag.

proc macros don't need to be pure in their implementation, only in their behavior

as long as each input of tokens can only yield a single output, it doesn't matter if it technically does impure operations like caching valurs in static variables.

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.