Compile-time introspection: when did you need it, how did you solve the problem without it?

If you ever needed compile-time introspection in Rust: I'm wondering what was your use case, and how did you solve your problem without compile-time introspection?

A limitation of Rust macros is that they don't let you introspect the types or code, you only get the ASTs (or rather, token trees) of your inputs.

This works fine for my use cases and it allows pretty advanced things already (for example, I generate lexers and parsers with proc macros, deriving serde traits seems to work fine for majority of the use cases), but there are alternatives, like Zig's comptime support of types, where you have access to things other than just the AST of your arguments.

I'm wondering if you ever had a use case where you couldn't just add a #[derive(...)] to a type, or just wrap some code with a macro (my_macro! { <code> }), and instead you wanted to e.g. iterate fields of a type in a third-party library in a macro. Or anything else that Rust's macros don't let you do.

Any examples would be appreaciated.

At various times, I've wanted:

  • A trait and attribute macro (or derive) combo that lets me see how large a type is, divided over stack and heap sizes for any type I care to inspect. Technically this should be feasible but it isn't quite as trivial as it might seem to actually get done.
  • a macro that lets me reason about types (which Rust macros will likely never support due to the relative order of macro expansion on the one hand, and name and type resolution on the other)
  • a macro that, when slapped on a fn, adds it to a dynamic registry. This would be useful for plugin systems