[HN Gopher] Let's write a macro in Rust
___________________________________________________________________
Let's write a macro in Rust
Author : hackeryarn
Score : 100 points
Date : 2025-10-10 15:57 UTC (7 days ago)
(HTM) web link (hackeryarn.com)
(TXT) w3m dump (hackeryarn.com)
| Y_Y wrote:
| They should try writing a spellchecker first, I found the article
| difficult to read because of the high frequency of typos.
| CaptainOfCoit wrote:
| The spelling mistakes seems almost intentional. I've noticed
| that a lot of publications that used to publish obvious AI slop
| now are doing the same, I'm guessing it throws off some "LLM
| detectors" or similar.
| spelunker wrote:
| It became distracting after the first two or three spelling
| errors, agreed.
| 127 wrote:
| Having done some Rust macros, the entire thing is such a huge
| hidden wart. Even very simple meta-programming in Rust need to be
| in their separate crate and has to use very complex syntax and
| invocation.
|
| Also the whole "don't write macros" is such a hilarious statement
| given that entire Rust ecosystem is built on them.
| airstrike wrote:
| I agree Rust macros are cursed in many different ways but
|
| _> given that entire Rust ecosystem is built on them._
|
| I don't think the people saying "don't write macros" are the
| same people building an entire ecosystem on top of them
|
| The iced crate has virtually zero macros by design.
| spoiler wrote:
| Eh. I agree there's complexity in them, and that a lot of the
| ecosystem indirectly use them because of derive macros, but I
| wouldn't call it a wart, and the "syntax" for writing proc
| macros isn't that bad if you use the syn/quote crates. I agree
| that decl macros usually make me sigh and open the docs though
| nicoburns wrote:
| > I agree that decl macros usually make me sigh and open the
| docs though
|
| Declarative macros are very like regex's: both in that at
| first they seem incredibly dense and arcane but once you work
| out how to read them they're actually very simple, and in
| that the syntax is literally similar in that it's a list of
| tokens that must match sequentially.
| pjmlp wrote:
| If regexp was simple there would not exist universities
| dedicating a semester to its theory and various
| implementation algorithms, alongside a famous quote. :)
| tialaramex wrote:
| Universities are teaching about the Regular languages and
| about Finite automata.
|
| So if we're saying this means regex isn't simple then -
| having observed that my University's Math course has
| _two_ semester long Number Theory courses we must
| conclude that the integers aren 't simple either, right ?
|
| And yet, the integers are simple enough that's where we
| start five year olds, by counting things.
|
| Simple and Shallow are not the same thing.
| pjmlp wrote:
| Indeed they aren't, that is why math has such a bad
| reputation in most countries, already at basic school
| levels.
|
| Recent article this week in German press, as per IQB-
| Bildungstrend 2024 results, one third of students fail in
| math exams.
|
| Which reinforces the common culture that math is hard,
| and it is to be expected not to have great grades at it.
| pjmlp wrote:
| It is its own can of worms, and maybe it is due to me using the
| language on and off since 1992, but I find what is possible in
| C++23, much easier for metaprogramming as the few times I had
| to understand trying to implement Rust macros.
|
| C++26 reflection will make this much easier, without having to
| depend in stuff like the syn crate.
|
| I wonder if going with two macro systems, each with its own
| syntax and approach, was such a good idea.
| IshKebab wrote:
| Maybe "don't write your own macros". I guess it's like C++
| template meta programming. If you find yourself using that a
| lot, it's probably a bad sign. But also Eigen is pretty great.
| TinkersW wrote:
| I dunno, the C++ template implementation of this macro would
| be far easier to read as it is just normal C++.
| IshKebab wrote:
| C++ templates are totally different feature. You couldn't
| do this with C++ templates.
|
| It was just an example of something that gets very hairy
| for library implementers but is still quite nice for users.
| ameliaquining wrote:
| Macros by example, which is what this post is about, don't have
| to be in a separate crate.
| Communitivity wrote:
| Bjarne Stroustrup said "C makes it easy to shoot yourself in the
| foot; C++ makes it harder, but when you do it blows your whole
| leg off". The same is true of Rust macros. When you need them
| they're awesome, but you should almost never need them - add a
| new macro as a very last resort.
| galangalalgol wrote:
| I agree with this, mostly because all the macros I would use
| are mostly written already. If const generic expressions
| arrive, a lot of those could go away too. In languages like
| lisp or zig where metaprogramming is a central feature, what do
| they do differently to make it better? Would those things have
| worked for rust?
| jacquesm wrote:
| That's a hilarious quote. Thank you.
| tcfhgj wrote:
| > The same is true of Rust macros.
|
| can't follow
| the__alchemist wrote:
| I like _using_ or _exposing_ macros when appropriate. They can
| simplify messy syntax, or prevent repetitions not allowed by the
| compiler directly. I can 't get the syntax for writing them to
| stick. So, I am happy with letting LLMs handle this.
|
| Sometimes the macros are for large chunks of code that uses a
| different type in a key place, or sometimes they're for cleaning
| up verbose function calls that will be made multiple times, so
| the important parts (e.g. the params) are obvious, instead of
| being mixed with boilerplate. In general, they're nice for
| papering over boilerplate. In particular, there's a pattern in
| embedded that involves a long expression with
| `try_into().unwrap()` for parsing bytes that I have a macro for.
| And another for managing RefCell<Mutex<Option>>> locks.
|
| I prompt the LLM with the working, but repetitive or boilerplate-
| laden code, and it macros it: "Please write a macro that stops
| the repetition in this code block: ```rust ```. Here's a example
| of calling it: `do_thing!(a, b, c)`
|
| Works every time!
| afavour wrote:
| > I can't get the syntax for writing them to stick
|
| Oh man, same. I wrote a macro crate then went back to it months
| later and couldn't make heads nor tails of anything I wrote.
| Something about the syntax just doesn't stick in my brain.
| iambacktomyword wrote:
| Interesting points -- I always thought macros were core to Rust's
| design philosophy. Do you think procedural macros will ever get
| simpler, or is the complexity kind of intentional for safety?
| iambacktomyword wrote:
| True, macros in Rust can feel heavy, but they also enable some
| powerful abstractions you just can't get otherwise. I guess the
| "don't write macros" advice is more about avoiding overuse than
| rejecting them completely.
| kachapopopow wrote:
| The only macro I ever use is json! or for applying 10 derives
| to more than 1 struct
| iambacktomyword wrote:
| So basically, macros are like dark magic -- everyone warns you
| not to use them, but half the ecosystem runs on them anyway
| iambacktomyword wrote:
| Yeah, Rust's macro system feels like a double-edged sword --
| amazing power, but with a big learning curve. The tooling and
| syntax definitely make simple meta-programming harder than it
| should be.
| tracker1 wrote:
| I liken it to a bit less extreme a version of the "don't write
| your own cryptography." Macros are definitely useful, and they
| are pretty widely used and adopted for several things. That
| said, actually creating your own tends to take a slightly
| different mindset than general application development.
| tialaramex wrote:
| So, this article is writing a "by example" macro, and my
| impression is that many comments here are thinking about Rust's
| procedural or "proc" macros and most particularly its entirely
| general procedural macros which are indeed arbitrary code
| executing in the compiler. That's not what the article is about
| at all.
|
| "by example" macros are actually just a fancier version of
| templating systems I'd expect most of you have used. Their
| biggest fancy feature is probably repetition, it's easy for
| Rust's macro to say if we've got a list of N things, we're going
| to emit this same code N times, but with each thing in turn
| filled out.
|
| The syntax doesn't look that much like the rest of Rust but
| that's mostly an ergonomic consideration, so you can distinguish
| between your macro and the code your macro is spitting out. In
| fact "at least the syntax is the same" is one of the few ways the
| procedural macros are simpler, since they're literally some Rust
| run by your compiler.
| BetaDeltaAlpha wrote:
| There are so many typos in here it makes it difficult to focus on
| the content. Is this a deliberate signal that the article was
| human written?
|
| I learned a lot regardless and I'm adding this to my blogroll.
| adregan wrote:
| One neat thing about the referenced chapter on macros from
| _Practical Common Lisp_ is that it 's chapters 7 and 8 of a 30+
| chapter book. You're only learning about variables in the chapter
| before and then macros. I like to think about that when
| conversations about macros always devolve into talking about how
| you should never use macros.
|
| Now apologies for the aside--and I know I'm likely wading into a
| very expansive topic and reducing it down to something simple--
| but why in Rust, if the types are known: struct
| Song { title: String, artist: String,
| rating: i64, }
|
| do you have to call `.to_string()` on things that look a lot like
| strings already? Song::new("Hate
| Me".to_string(), "Blue October".to_string(), 9)
|
| Couldn't the compiler just do that for you?
| steveklabnik wrote:
| They're different kinds of strings, the String string means
| heap allocation, and Rust never allocates in the language, so
| the compiler automatically invoking allocation routines for you
| wouldn't be good, in Rust's view.
| throwawaymaths wrote:
| the reasons to not use macros have to do with hygeinicity? --
| macros can do things like introduce code that is hard to
| understand, perhaps a dynamically named function, or a
| difficult to chase dependency or import... moreover these can
| make grepping harder.
| LtdJorge wrote:
| Because to_string allocates. And if a function requires a
| String (owned), it cannot accept a str reference (borrowed), it
| would defeat the purpose of the strong type system. String is
| moved, while str is passed by reference.
|
| There is the exception of Deref. If the function requires type
| A, and you pass it type B, which Derefs into type A, the
| compiler will Deref it for you. But that is zero cost and panic
| free, whereas allocating (and copying) an owned type from a
| reference isn't. In Rust you have to be explicit.
|
| Anyway, using String in function signatures is most often not
| the best choice. If you will internally be required to use a
| String, it's better to ask for a type "impl Into<String>",
| you'd call into() inside your function. And in the most common
| case, where you require a &str or can convert to your type from
| it, the best choice is an "impl AsRef<str>" and you can call
| as_ref() on it to get a str reference, which you can wrap in a
| Box, Rc, Arc, String, or your custom type, or pass it directly
| to other functions. All of those, Box<str>, Rc<str>, etc
| implement both traits.
|
| Using impl Trait, you avoid having to declare generic
| parameters.
| gnatolf wrote:
| If only one could construct a macro to solve the boilerplate
| of AsRef<str> etc ;)
| LtdJorge wrote:
| To me, there's not much difference between:
| fn foo(name: &str) {}
|
| and*: fn foo(name: impl AsRef<str>) {}
| marcosdumay wrote:
| That's the price you pay for a low level language where things
| like memory usage are visible for you to optimize.
|
| If you don't see the gain, maybe Rust is not the right language
| for your use-case.
| estebank wrote:
| To expand on the sibling comments, this is why Rust doesn't do
| implicit type conversions that could allocate:
|
| https://groups.google.com/a/chromium.org/g/chromium-dev/c/EU...
| rootnod3 wrote:
| To be fair, as much as Rust macros are nice, it is a losing
| battle to go up against the Lisp macro system. Lisp might have
| its flaws, but macros in Lisp are second to none.
|
| When to use them is a whole different story. But examples of
| macros I like are `when` and `unless`. Yes, simple, but they
| show a nice example of their power.
|
| For more complicated once, love it or hate it, but the `loop`
| macro is probably THE prime example of a powerful macro.
| QuantumNomad_ wrote:
| When you have a &str (like "Blue October") and pass it to
| something that wants a String, you can do .into() instead of
| .to_string()
|
| It's shorter to write and takes up a little less space on
| screen to. I almost always use .into() when like in your
| example I initialize String field members of a struct in Rust.
| tialaramex wrote:
| Those "things that look at a lot like strings already" are
| literals and thus constants, with the type `&'static str` which
| is how Rust spells an immutable reference to a string slice
| which lives forever. So, Rust is promising that the string
| slices "Hate Me" and "Blue October" exist, and in practice
| probably if you look inside the resulting executable it says
| "Hate MeBlue October" or similar.
|
| On the other hand the String type is a growable array of bytes
| used to assemble strings, it's an owning type, so Song will
| _own_ those two Strings, they can 't go away or be altered via
| some other force. Owning a string would allow you to add to it,
| change it, or discard it entirely, you obviously can't do that
| to the text baked into the executable, so if you want to own a
| String it will have to be allocated at runtime.
|
| You can also write "Some Words".to_owned() for example to the
| same effect.
| TheSoftwareGuy wrote:
| For those interested, this blog post also has a part 2 and 3:
|
| https://hackeryarn.com/post/rust-macros-2/
|
| https://hackeryarn.com/post/rust-macros-3/
| gnatolf wrote:
| Which confusingly are linked to on the 'prev' link at the
| bottom of each part...
| stared wrote:
| `macro_rules!` - I always read "rules" as a verb.
| darkest_ruby wrote:
| Omg, was it not a verb all this time??!
___________________________________________________________________
(page generated 2025-10-17 23:01 UTC)