19 comments

[ 1.9 ms ] story [ 44.7 ms ] thread
(comment deleted)
They should try writing a spellchecker first, I found the article difficult to read because of the high frequency of typos.
Thanks for pointing that out. This is really embarrassing, but I recently switched to Helix as my editor and had a conflicting config that partially broke the spellchecker in my blog.

Should be a lot better now.

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.

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.
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!

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?
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.
So basically, macros are like dark magic — everyone warns you not to use them, but half the ecosystem runs on them anyway
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.
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.

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.

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?
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.

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.

`macro_rules!` - I always read “rules” as a verb.
(comment deleted)