There they jam a login_required! macro in the middle of the code. Not entirely sure what Backend is but, it's possible to figure that out with a bunch of clicking around.
But this breaks my code entirely. Here's what my editor shows.
Macros have important use cases, but it’s not clear why this one needs to exist. Over-use and under-documentation of macros in a library API should be taken as a reason to avoid that library.
I think that part of the problem is Rust relying on declarative macros to compensate gaps in the language. The standard library contains many examples: how many times have I been lost when I was trying to find the definition of a method, only to find several layers of macros used to define methods on several types? Typically methods on integer types.
It's also quite messy to read and understand. It's fine if it's used to generate a simple object, but when it becomes part of the code logic, it makes for painful understanding and code maintenance.
In this case, it looks like it's been (ab)used because Rust doesn't provide us with optional method parameters, though the doc isn't very loquacious.
These are not even a gaps, unfortunately. Rust have decreed that all the “gnarly” things that are handled with templates in C++ and comptime manipulations in Zig shouldn't exist… but world declined to comply and macros can be used to solve that dilemma.
The fact that they make code hard to read is unfortunate but there's cargo expand that may help in such cases.
Macros are what macros have always been in any language (or even applications):
A great tool to solve repetition but also an evil weapon for total obfuscation when misused.
Somehow reminds me on Lisp or even functional languages like Haskell: You can write higher functions and juggling with mathematical constructions that makes a program total incomprehensible.
The only thing we can do about it (when tempted to write a macro) is to try to find another solution involving traits, and be willing to tolerate code duplication when a macro is the only way to avoid it.
I'm guessing this is really not going to change, but I'd say having an obtuse meta-language core to a lot of functionality is really one of the worst things about Rust.
I'd say it's a lesser evil in some cases, when it's not possible to do otherwise. I'd rather use println! and vec! than the equivalent Rust code (which may still include built-in macros). It usually provides a more readable code where the macro is used, at the expense of readability of the macro itself, so at the expense of intelligibility of how it works.
The syntax has been designed to be distinct from Rust's, I think, so that it wouldn't be too confusing. The result is still hard to read unless you're familiar with it, especially in some complicated cases. At least, it's less problematic than C's preprocessor, which is a nasty hack and can produce even nastier side-effects.
On the other hand, declarative macros are a great tool when you need to instantiate objects in your unit tests or even in an API, like vec!. It's also generally handy in tests, where you must often repeat similar things. There, it doesn't bother the crate users since it's not public.
The more insidious problems I see with it, beside the lack of readability in std, are:
When it replaces a gap in the language, it might become a reason to postpone the original issue indefinitely. At this point, the macro has already been adopted, anyway. Note that it's sometimes per design because changing the language would make it less safe, or it would be too complicated to change, or the result would be less performant (e.g. complexity moving from compile time to runtime).
It might be OK when the macro is in the standard library, like format! or vec!, but when it's not (no hashmap!, hashset!, btreemap!, btreeset!, ...), everyone starts using their custom macros or one of the crates providing them [1]. They often look the same, but they don't all behave the same way, so that's a tiny risk. Or the crates are not maintained, any more.
Then, as you said, editors and IDEs struggle with it because they have to expand the code and analyze it. It's not rhetorical: in a series of unit tests, I finally had to replace many small declarative macros that were instantiating the objects under test because of the significant impact it had on the IDE performances.
No, you're not alone. Couple years ago I also tried to figure out what's happening with my args in axum's macros, and it made no sense at all. Some more attentive reading of the docs gave implicit hints to what I had to do. Axum's magic is heavy.
On the other hand, when I checked how heavy the output code is, compared to other Web frameworks, Axum was the best one. (My simple tests)
Complexity have to live somewhere. If you refuse to have complexity in macros then it's often pushed into the code and then compiler may or may not optimize it away.
It always seemed to me that code is pretty complicated stuff. Now you want macros, a way to write code, which is complicated, in code, which is complicated. Seems to me the resulting mess cannot help but be complexity squared.
Well, what do you expect? Macros are the simplest way to do practical metaprogramming. Generics and traits are nice but they expect completely orthogonal taxonomy of everything… our world is too messy for that. Macros give you practical way of handling these things.
Think about what would you need to implement serde without macros, Java-style… a lot of things that are either rejected on purpose or simply not implemented yet.
Regarding the book / tutorial part, Writing Powerful Rust Macros (by Sam Van Overmeire) is a very good book, but it's more focused on procedural macros. There's a good tutorial for declarative macros too, though it doesn't show more hairy cases or how to work around the recursive limitations, so I'd recommend it mostly if you're also interested in procedural macros.