Hacker News new | past | comments | ask | show | jobs | submit login

Macros themselves are a terrible hack to work around support for proper reflection.

The entire Rust ecosystem would be reshaped in such fascinating ways if we had support for reflection. I'd love to see this happen one day.






> Macros themselves are a terrible hack to work around support for proper reflection.

No, I'm not sure where you got this idea. Macros are a disjoint feature from reflection. Macros exist to let you implement DSLs and abstract over syntax.


If you look at how macros are mostly used, though, a lot of that stuff could be replaced directly with reflection. Most derive macros, for example, aren't really interested in the syntax of the type they're deriving for, they're interested in its shape, and the syntax is being used as a proxy for that. Similarly, a lot of macros get used to express relationships between types that cannot be expressed at the type system level, and are therefore expressed at a syntactic level - stuff like "this trait is derived for all tuples based on a simple pattern".

There are also proc macros just for creating DSLs, but Rust is already mostly expressive enough that you don't really need this. There are some exceptions, like sqlx, that really do embed a full, existing DSL, but these are much rarer and - I suspect - more of a novelty than a deeply foundational feature of Rust.


I'm not intending to say that reflection is useless, but rather to say that judging macros harshly due to not being reflection would be incorrect.

But the point is that if you've got reflection (and an expressive base language, and a powerful enough type system, etc), you probably don't need macros. They're a heavy mallet when you almost always need a more precise tool. And the result of using macros is almost always worse than using that more precise tool - it will be harder to debug, it will play worse with tools like LSPs, it will be more complicated to read and write, it will be slower, etc.

I think macros are a necessarily evil in Rust, and I use them myself when writing Rust, but I think it's absolutely fair to judge macros harshly for being a worse form of many other language features.


No disagreement on your point, but this is a different argument than claiming that macros are an ugly hack to workaround lack of reflection.

Because Rust lacks reflection macros are used to provide some kind of ad-hoc reflection support, that much we agree... but macros are also used to provide a lot of language extensions other than reflection support. Macros in general exist to give users some ability to introduce new language features and fill in missing gaps, and yes reflection is one of those gaps. Variadics are another gap, some error handling techniques is yet another, as are domain specific languages like compile time regex! and SQL query macros.


But the point is that almost all of the common places where macros are used in everyday Rust could be replaced by reflection. There are exceptions like some of the ones you mention, but these are clever hacks rather than materially useful. Yes, you can write inline SQL and get it type checked, but you can also use a query builder or included strings and get the same effects but in a much less magical and brittle package.

Macros in Rust are primarily a tool to handle missing reflection capabilities, and them enabling other code as well is basically just a side effect of that.


They are disjoint, but the things you can use them for overlap a lot. In particular, I'd dare say a majority of existing `#[derive]`-style macros might be easier to implement in a hypothetical reflection layer instead.

Instead of taking a raw token stream of a struct, parsing it with `syn` (duplicating the work the compiler does later), generating the proper methods and carefully generating trait checks for the compiler to check in a later phase (for example, `#[derive(Eq)] struct S(u16)` creates an invisible never-called method just to do `let _: ::core::cmp::AssertParamIsEq<u16>;` so the compiler can show an error 20s after an incorrectly used macro finished), just directly iterate fields and check `field.type.implements_trait(Eq)` inside the derive macro itself.

That said, that's just wishful thinking - with how complex trait solving is, supporting injecting custom code in the middle of it (checking existing traits and adding new trait impls) might make compile time even worse, assuming it's even possible at all. It’s also not a clear perf win if a reflection function were to run on each instantiation of a generic type.


You got it the other way around. Macros can implement (nearly) anything. Including some form of opt-in type introspection via derive macros.

They weren't a hack to get reflection. They are a way to codegen stuff easily.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: