3 ms·
I don’t really like that procedural macros are a thing. Rust isn’t quite at the Go level of forcing style, but it’s closer to it than it is to C++ or C, and I t
by nickysielicki 7y ago
I don’t really like that procedural macros are a thing. Rust isn’t quite at the Go level of forcing style, but it’s closer to it than it is to C++ or C, and I think the ability to implement DSLs with procedural macros makes it harder to look at code and understand what it does.
I guess I’d rather have the feature for when it’s truly necessary than exclude it from the language entirely, and just hope that people use it with forethought.
- steveklabnik 7y agoNo procedural macros means that libraries like serde and diesel cannot exist. Or at least, they would have to exist in a much worse form. This was true before Rust 1.15, when the first parts of procedural macros were stabilized. I remember the release specifically because things got so much better after this was possible.
- lmm 7y agoA macro is a bug report against the language; Rust is crying out for a way to treat structs generically (i.e. what frunk does with LabelledGeneric), but that only needs to be implemented once. There's no reason serde and diesel couldn't be built on top of frunk (that's how many things work in the Scala world: specific libraries like circe or pureconfig don't write custom macros, they just pull in a dependency on Shapeless) - and then once there was a clear consensus on the right way to do it, LabelledGeneric or equivalent could be made a standard part of the language and no macros would be needed at all. Maybe macros make sense as a pragmatic way to allow prototyping of future language features. But language designers can, and IMO should, work towards making them unnecessary in the production-code ecosystem, because the disadvantages for readability and tooling are very real. And that's only going to get worse as more advanced tooling (IDEs, profilers etc.) becomes available and people rely more heavily on those tools to be deeply integrated with the language, because those tools are never going to be able to understand ad-hoc macros.
- Arnavion 7y ago>Rust is crying out for a way to treat structs generically (i.e. what frunk does with LabelledGeneric), but that only needs to be implemented once. There's no reason serde and diesel couldn't be built on top of frunk [...] What frunk does at runtime (well, its proc macros are still compile-time, but the code that makes use of the metadata is runtime), syn does at compile-time. I can understand that it's nice to have a simpler model where everything happens at runtime, but the things that proc macros can do include things that matter to the typesystem. For example, some of the macros I've written generate new types based on the input tts. You can't do that at runtime. >And that's only going to get worse as more advanced tooling (IDEs, profilers etc.) becomes available and people rely more heavily on those tools to be deeply integrated with the language, because those tools are never going to be able to understand ad-hoc macros. The mainstream tools (rls and rust-analyzer) get their information from the compiler, and thus have access to the expansion of the macro rather than just the macro invocation.
- lmm 7y ago> What frunk does at runtime (well, its proc macros are still compile-time, but the code that makes use of the metadata is runtime), syn does at compile-time. I can understand that it's nice to have a simpler model where everything happens at runtime, but the things that proc macros can do include things that matter to the typesystem. For example, some of the macros I've written generate new types based on the input tts. You can't do that at runtime. Again, though, that's something you want to be able to do in a standard first-class way in the language, rather than needing a custom macro. Depending on the use case you either want higher-kinded types (for transformations like producing a "patch" version for a given record, where all members are (recursively) optional) or dependent types (for transformations where you really just want to do a bunch of specific type->type cases). It's really not rocket science - Scala has been doing this stuff for years already. > The mainstream tools (rls and rust-analyzer) get their information from the compiler, and thus have access to the expansion of the macro rather than just the macro invocation. Sure, and that helps, but the tool can't possibly know what the full relation between input and output is (because by definition the macro is an arbitrary function), so it's never going to be as reliable as first-class code.
- hathawsh 7y agoFWIW, I chose to learn Rust specifically because of the metaprogramming afforded by procedural macros. The code I write leans heavily on (de)serialization, frequent error handling, declarative programming, and other automation. I can hardly imagine how verbose my code would be without procedural macros. One of the first things I wrote was a procedural macro. Yeah, I like to learn things the hard way, but I'm glad I did! Once I got past the confusing split between proc_macro::TokenStream and proc_macro2::TokenStream, I found that using "syn" and "quote!" is really quite enjoyable.
- bcheung 7y agoSure it can be misused, but honestly, all the macros I've seen so far make sense that they are macros and they make things easier to read and write. I'd rather have a small simple concise 1-liner, than being forced to write 20 lines of code otherwise. In Go you have `if err != nil` on every other line it seems and that makes things hard to read. The `try!` macro which later became the `?` makes things much easier to read IMO. Especially when you have to drill into something nested.