top | item 32507659

How I learned to stop worrying and love macros

140 points| magnio | 3 years ago |zdimension.fr | reply

52 comments

order
[+] tomcam|3 years ago|reply
> "macros" are probably the first thing you're taught to be afraid of (second only to "moving a picture in a Word document"),

Next time, trigger warning please! I still have PTSD from that time I tried to text flow around an image correctly back in ‘08 on deadline. I just try to think of God and country instead…

[+] blippage|3 years ago|reply
> Here, there's a conflict between the lexical scope of the function (main) and the macro (SWAP).

It might interest to some to know that GCC has a macro called __COUNTER__ (apparently also implemented by clang and MS), which auto-increments its value. You can then create something like a gensym to avoid conflict.

Here's an implementation of the "defer" keyword in C++:

    template <typename F>
    struct privDefer {
     F f;
     privDefer(F f) : f(f) {}
     ~privDefer() { f(); }
    };

    template <typename F>
    privDefer<F> defer_func(F f) {
     return privDefer<F>(f);
    }

    #define DEFER_1(x, y) x##y
    #define DEFER_2(x, y) DEFER_1(x, y)
    #define DEFER_3(x)    DEFER_2(x, __COUNTER__)
    #define defer(code)   auto DEFER_3(_defer_) =     defer_func([&](){code;})
It was written by gingerBill, author of the Odin programming language.
[+] zdimension|3 years ago|reply
TIL, I'd never have thought something line gensyn could be possible using C macros (though this is an extension). This is both amazing and horrible
[+] leni536|3 years ago|reply
I would advice against using __COUNTER__ or macros based on that in header files. It's an ODR-violation footgun.
[+] jefftk|3 years ago|reply
The post starts slow, but is worth reading for when they get to embedding C and VB Script directly in rust programs.
[+] snovv_crash|3 years ago|reply
X-macros in C [1] are great for doing compile-time code generation from tabular data. Especially for things like pinouts and their respective capabilities on microcontrollers, I haven't seen any other language give such a succinct, easily maintainable way of interacting and representing data such that it can be used for full function generation that an IDE can autocomplete names, types etc.

Sure there are other "newer" ways but they invariably involve so much boilerplate that you might as well just write all the code by hand.

1. https://en.wikipedia.org/wiki/X_Macro

[+] tragomaskhalos|3 years ago|reply
One nice feature of Rust macros that isn't usually trumpeted is that it allows varargs to be punted out of the core language. Consider:

- Varargs are, generally speaking, a pain and a wart, especially in statically typed languages

- Printing stuff is horribly unergonomic without varargs (C++'s iostream << is arguably a get-out here, but is widely hated),

- There are no other compelling 'mainstream' uses for varargs afaik

By having print as a macro, Rust can swerve this particular tarpit. Not suggesting that it's the reason for macroising print, but it's a welcome bonus nevertheless.

[+] valenterry|3 years ago|reply
They are only a wart, if the typesystem is not powerful enough (such as Rust's typesystem). With dependent types, this is a non issue. In fact, you can implement something like typesafe "printf" as a regular user-defined function in a language like Idris and to some degree even Scala.

See e.g.: https://gist.github.com/chrisdone/672efcd784528b7d0b7e17ad9c...

[+] tialaramex|3 years ago|reply
Everything which needs the format-arbitrary-parameters feature in Rust is built out of macros, so yes it's the reason that println! is a macro. Even with no_std where you don't have an Allocator, and so you can't use format! to get Strings (because there aren't any Strings) you still have mechanisms built out of macros, so you could e.g. write six u16s and a f32 to the serial port on your embedded controller (you could write a &str, but since you can't allocate Strings it'll have to be some pre-defined value like "Error" or "OK").

These functions are especially tricky because we want to have a variable number of arguments with arbitrary types. We want to be able to print two bytes, a string, an array, and six different arbitrary data structures we decided were important.

There are lots more applications for function(a, b, c, d, ...) where a, b, c and so on are all the same type. It might be nice to define min() and max() over these parameters for example. But cases like print-format where it's sane to choose unrelated types are rarer. Languages where I can write min("Cheese", 4, tmpfile, -Inf) have lots of other problems that outweigh the potential usefulness of such constructions.

I expect that if somebody can write a sound but ergonomic varargs story for safe Rust it'll get introduced, even if they can't solve print-format it's still useful as I showed above.

[+] dataangel|3 years ago|reply
Macros totally fail at keeping varargs out of core because of generics. Try writing emplace_back in Rust, or anything else that would need to generically do placement new.
[+] charlieflowers|3 years ago|reply
This is hilarious in addition to being technically accurate and insightful.
[+] epgui|3 years ago|reply
If anything, this post has only succeeded in making me even more anxious about all languages that aren't built on s-expressions...
[+] klysm|3 years ago|reply
S expressions without types make me nervous
[+] abrax3141|3 years ago|reply
All languages are built on s-expressions. (Aka ASTs) Lisp is just the only one proud of the truth.
[+] mkarliner|3 years ago|reply
Great post, thanks. Finally makes me want to learn Lisp
[+] zdimension|3 years ago|reply
Glad you liked it! If you want to learn Lisp, I'd recommend something like Racket (implementation of Scheme, a dialect of Lisp), it's a "batteries included" Lisp that should be easier for a beginner to learn. The built-in code editor (DrRacket) is, eh, good enough.
[+] Noler|3 years ago|reply
Teach me how to stop worrying
[+] koheripbal|3 years ago|reply
Try to visualize yourself lying in bed 5 minutes before your death. Imagine you know you're dying, set a timer, lay down, and really try to put yourself in your own shoes.

Let the panic set in, don't try to change the subject. Imagine as the seconds slip by that you struggle to reflect on all the parts of your life. 5 minutes can seem like a long time, but when it is your last few seconds they vanish quickly.

Really be in that moment. Take it seriously. Embrace the terror.

When the timer goes off none of the problems in your life will seem one-tenth as serious as before.

[+] slaymaker1907|3 years ago|reply
Only allow ones that are hygienic. Maybe also have a tool like DrRacket which will show you stage by stage what macros expand to (including features like not expanding common ones like "define" in Racket or "println!" in Rust). I think the latter in particular is important since debugging what code you're generating is very important.
[+] evanmoran|3 years ago|reply
It’s ok to worry, it just means you care. Find people who will worry and care with you about the same things you do and it will be feel better. Partially because you’ll find more important things to worry about, and partially because talking about it will help you cope!
[+] alpaca128|3 years ago|reply
Little technical nitpick: the code blocks on the linked site are unreadable without JS enabled. They have a white background & light grey text as default.
[+] zdimension|3 years ago|reply
Sorry about that! I'm still trying to find a way to get good support for dark mode with a dynamic dark mode switcher, and it seems that the current one I'm using (from V8 labs) behaves... erratically when JS isn't enabled. I'm working on it!
[+] nine_k|3 years ago|reply
The post consists of two parts: educational (what macros are) and entertaining (use and abuse of macros in Rust).

I recommend both, and the punch... line made me LOL IRL.

[+] birdyrooster|3 years ago|reply
Macros and (nigh) mandatory code generation and boilerplate are proof your language sucks. It might go fast, but it sucks.
[+] kastagg|3 years ago|reply
Doing some calculations at compile time is useful. What sucks is when the language for expressing this is difficult to think about and interacts poorly with normal code.
[+] quickthrower2|3 years ago|reply
It might prove that the language has decided not to have too many language features.
[+] logdahl|3 years ago|reply
I dont know, I see macros and code generators as the one tool to battle boilerplate. Functions often have boilerplate syntax rules. Macros do not :^)
[+] zelphirkalt|3 years ago|reply
What sucks much more is, when one cannot extend the language properly and it needs a language commitee to change anything. Or when the way to extend it is so mistake and error prone, that one cannot get it right, needs many eyes viewing it for a few years, before it becomes probably correct.