|
|
Subscribe / Log in / New account

Too complex for my small brain

Too complex for my small brain

Posted Nov 6, 2024 18:54 UTC (Wed) by NYKevin (subscriber, #129325)
In reply to: Too complex for my small brain by khim
Parent article: Safety in an unsafe world

Rust generics are limited, in my understanding, because they don't want people to start using TMP to do things that are better accomplished with proc macros (and also for other reasons like "overload resolution is difficult to reason about"). Unfortunately, it is possible to do many (not all) of those things with nested/recursive macro_rules! instead of proc macros, and that's... probably not quite as bad as TMP, but it gives TMP a run for its money.

Definitions for people who don't Rust/C++:

* TMP: Template meta-programming, taking advantage of the C++ template system's overload resolution and backtracking to write arbitrarily complicated compile-time logic.
* Proc macros: Procedural macros, or macros that work by taking a stream of tokens, running it through some normal-ish Rust code at compile time, and feeding the result directly to the compiler.
* macro_rules!: Macros by example, or macro definitions that vaguely resemble C preprocessor macros (but with namespacing, greater flexibility, and stronger guardrails against doing things that don't make sense).
* Nested/recursive macro_rules!: Macro definitions that expand into calls to themselves and/or into additional macro definitions (which are subsequently used by the outer macro to do branching and the like). Usually involves the outer macro matching :tt and passing it along to the inner calls for further dissection.


to post comments

Too complex for my small brain

Posted Nov 6, 2024 21:42 UTC (Wed) by khim (subscriber, #9252) [Link]

> Rust generics are limited, in my understanding, because they don't want people to start using TMP to do things that are better accomplished with proc macros

They are limited by the desire to get better diagnosis in the case of error. It's the same story as with statically typed language and dynamically typed languages: it's easier to write code in a dynamically typed languages but harder to debug it.

Metaprogramming template language in C++ is dynamically typed with optional static annotations (close to python in spirit if not in syntax).

Metaprogramming generics language in Rust is statically typed because of the best intentions: to make sure error messages would be readable. Unfortunately we all know where this road leads: error messages are kinda-sorta understandable if you can easily express what you need in an additive form, but if you couldn't… ho, boy that's where the fun begins.

Thankfully when we are talking about keeping invariants unbroken you don't really need that machinery and can safely ignore it. Instead of working with types in Rust, you can just go and use constant verifiers (like in my example above). It's much easier to encode “business logic” in const expressions because they don't need to be additive and you have plenty of normal logical operations there.

That's not a substitute for what one can do in C++ (because in C++ you can go back to types after working for a bit with constants), but it's more than enough to handle these “invariant verification” cases where you only need one of two ourcomes: code compiles (and, hopefully, works) or code doesn't compile (and you go and fix it).

That's what tilted the decision for me: 99% of time I need complex metaprogramming simply to reject the bad code, not to actually play type tetris. Verification with const is enough for that.


Copyright © 2025, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds