Open
Description
I'm tinkering a bit with procedural macros and encountered a problem that can be solved by keeping state in between proc macro invocations.
Example from my real application: assume my proc-macro
crate exposes two macros: config! {}
and do_it {}
. The user of my lib is supposed to call config! {}
only once, but may call do_it! {}
multiple times. But do_it!{}
needs data from the config!{}
invocation.
Another example: we want to write a macro_unique_id!()
macro returning a u64
by counting internally.
How am I supposed to solve those problems? I know that somewhat-global state is usually bad. But I do see applications for crate-local state for proc macros.
Metadata
Metadata
Assignees
Labels
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
abonander commentedon Mar 8, 2018
Statics and thread-locals should both be safe to use as the proc-macro crate is loaded dynamically and remains resident for the duration of the macro-expansion pass for the current crate (each crate gets its own compiler invocation). This is not necessarily stable as eventually we want to load proc-macros as child processes instead of dynamic libraries, but I don't see why they wouldn't be kept alive for the duration of the crate's compilation run anyway.
durka commentedon Mar 8, 2018
@abonander I don't think this is reliable for two reasons:
Proc macros may not be run on every compilation, for instance if incremental compilation is on and they are in a module that is clean
There is no guarantee of ordering -- if
do_it!
needs data from allconfig!
invocations, that's a problem.matprec commentedon May 28, 2018
Adressing ordering
Declare dependency of macros to enable delaying of macro execution. In practical terms, think of macro
Foo
andBar
. By declaring macroBar
depending onFoo
, all invocations ofFoo
must complete before any invocation ofBar
.E.g.
Adressing incremental compilation
A persistant storage, maybe a web-like "local storage", per proc-macro-crate? This would store and load a byte array, which the user could (de-)serialize with e.g. serde
fn set_state(Vec<u8>)
,fn get_state() -> Vec<u8>
Don't know about access though, how would it be provided to the proc macro crate? Global Memory? Wrapped in a Mutex?
Emerging questions
Thermatix commentedon Apr 25, 2019
Has there been any movement on this issue?
oli-obk commentedon Apr 25, 2019
The problem with such a scheme is that invocations of the same macro are still not ordered, so you can easily end up in a situation where the order of invocation changes the result.
If an ordering scheme between proc macros is implemented, one could consider giving
bar
read access tofoo
's storage. Though, this would depend onfoo
only ever being called once per crate (due to the output of multiplefoo
calls having unspecified order).Thermatix commentedon Aug 9, 2019
Perhaps I'm missing some nuance or information but, what if when you defined local storage you also had to define any and all files affected by this? Then when you re-compiled it would then scan those files for changes and re-compile as appropriate?
Whilst I'm all for the idea of automating where possible, the advantage is that you can now get a clear list of files involved, and it would provide some working functionality that would provide what this issue is trying to solve, even if it's not perfect, so long as it's reasonably ergonomic (despite having to list the files) it should be good enough.
Yes, you do have to define each file that gets affected, but no doubt that there is a way to automate even that.
ZNackasha commentedon Dec 20, 2019
I would love to have this future to solve the PyO3 add_wrapped requirement.
https://github.com/PyO3/pyo3
andrewreds commentedon Mar 20, 2020
A different approach. What do people think?
Stateful Macros
(I need better names, syntax etc. But the idea should be there)
Have a "stateful macro", which is a compile time struct.
Things run in 3x main steps:
note: Changes to the values of delayed symbols don't require recompilation of the crate using it.
Another example: for the sql! macro, it may want a unique id, that it can reference this query to the db by. The sql! macro could insert a symbol who's content is the position the query got inserted into the set (evaluated in stage 3). The sql! macro would not be able to see the contents of this symbol, but can inject it into the code.
compiler wise, crates are compiled independently like normal till just before they need to be linked. The compiler would then:
Use cases
Addressing points raised
Other points of note
steveklabnik commentedon Mar 22, 2020
To me, it feels like:
38 remaining items