Macros in Rust aren't only confusing for beginners, because it has a completely different syntax (well, at least macro_rules! does; idk about proc macros) than the one you use for the rest of your program.
Even _if_ you draw the line between macro writers and users, you've effectively made macros a black box you're not supposed to look into. Having trouble debugging anything related to a custom derive or a macro? Too bad, macro's are hard. This is (a) not useful and (b) not necessary, as Zig clearly shows.
I think Rust made the C++ mistake of trying to accommodate everyone by having both low level control of things, but not too low level since that's dangerous, so we'll come up with some rules that you can't break, and oh by the way the rules aren't really ready yet, oh, and if you're not used to dereferencing pointers, don't worry we have this deref trait, what's a trait you say? and so on and so on.
It's effectively a barrier to entry, which, ironically, is a thing the Rust community tries really hard to combat.
If you mix a bunch of nice colors, blue, red, green, turqouise, purple, orange, eggshell white, you just get ... brown.
How? My impression is that they are not trying to do the language easier at every release. Contrarily, I see more new features added all the time (which is a good thing if Rust is your thing).
What I see is top-quality documentation. No doubt about it. Perhaps they focus on quality learning material, but the truth is the more I read, the more I scratch my head thinking "what is this construct and when and why do I need to use this?". Then overchoice[1] anxiety kicks in and I go back to zero, that is, my good old C.
I understand why it is this way but it is very much not ergonomic
The entire macro space in Rust (just like async/await) is in MVP status: they are available and useful already, but their current feature set and approachability isn't the end-state.
I know some will read that and think to themselves "Great! More changes to the language! See, they can't help themselves.", but these changes are about removing restrictions and tapering edges.
Appeal to authority much?
Actually, type safety can be quite useful in such a context: it allows the person who designed the types in use to enforce certain restrictions on the developer who is hurriedly adding code to an unfamiliar codebase, thus reducing the requirement for that developer to understand larger issues in the system.
No doubt type systems can allow you to write safer, more robust code. But they can also allow you to introduce new risks to your codebase, one of which is difficult to read/understand code.
But there are a lot of quite frankly crazy choices, like making (milliseconds) an integer type that is incompatible with numbers. This means to multiply an time with a non-constant integer, you have to cast an integer to millisecond type first, then multiply. Which breaks the brain of a scientist like me and makes me want to throw my computer out the window.
My daily driver is Elixir, and it's not impossible to jump into totally foreign code and debug it. In fact, just the other day I did an interview where the 20 minute technical problem was drilling down from the frontend into the backend of a new-to-me system and finding the logic bug in a database query that was causing the wrong data to be surfaced.
It's not about how effortless the code is to write, but the total lifecycle effort. This includes for example maintenance, security, extending and on-boarding new team members.
I think one of the problems with this mindset is, you are only assuming the case with you have full control on the code-base e.g. writing thing from scratch so that you can only use those features in rust that you feel comfortable with, in other cases, you have little control on what others use.
I've read that as a (minor) complaint that Zig doesn't have Rust's pattern matching, not that there's anything wrong with pattern matching (and that Zig's switch still does 90% of what the author uses Rust's pattern matching for).
It's not necessarily bad. Do you prefer to put more effort into your program before it compiles, or debug it after it's up an running? Do you want a static guarantee, or do you trust yourself to get it right? These are fair trade-offs.
It strikes me that you start off meaning to rebut the author's point and end up supporting it.
I don't consider myself an expert in Rust, but hell, the borrow checker was always helpful. I.e. even if it prohibited a sound program, it explained its reasoning at length. It was easy to fix the issue even if it came.
Macros are meta programming, of course they are hard. And even then, there are ways around. Like cargo-expand, it makes procedural macros easier to reason about.
I know what a class and what an interface is, but Rust doesn't have these. It has a struct, which is kinda like a class without methods? It has a trait, which is kinda like an interface but with implementations for methods? It has ... implementations... which kinda make a struct to a class, but not really.
I don't mean to hate here, but these are all things that need to be understood in some kind of way.
But not much on traits and ADTs(?).
Can you recommend some general learning resources on that?
ADTs are Abstract Data Types[1][2][3]:
> ADT is implementation independent. For example, it only describes what a data type List consists (data) and what are the operations it can perform, but it has no information about how the List is actually implemented.
In the context of Rust it means that the traits, structs and enums are ADTs, while the impls are Data Structures.
Having structs and enums be the way they are in Rust (simplistic and with little extensibility beyond implementing traits) is that pattern matching[4][5] and destructuring is cheap and built in. Pattern matching becomes specially useful when combined with sum types/tagged unions/enums[6][7][8]. On the other corner you have Scala which lets you implement specific interfaces to allow structuring and destructuring for arbitrary types, but that has the same problems as overriding constructors in C++: the performance implications of pattern matching is impl dependent and hidden at the point of calling.
[1]: https://softwareengineering.stackexchange.com/questions/1487...
[2]: https://en.wikipedia.org/wiki/Abstract_data_type
[3]: https://abrickshort.wordpress.com/2005/03/06/abstract-data-t...
[4]: https://en.wikipedia.org/wiki/Pattern_matching
[5]: https://docs.scala-lang.org/tour/pattern-matching.html
[6]: https://www.schoolofhaskell.com/school/to-infinity-and-beyon...
[7]: https://chadaustin.me/2015/07/sum-types/
[8]: https://stackoverflow.com/questions/2502354/what-is-pattern-...
I'm basically looking for a comparison of classes and ADT, I guess.
You said, applying your class-based way of doing things didn't work, so I asked myself how would you do it the ADT way.
The distinction is similar to database design in SQL: the Schema is how the data is laid out and what the relationship between tables is (ADTs) while the queries is the operations performed on them (traits). On the other hand, in OOP there's a higher reliance on encapsulation, making behavior an integral part of what the class is and using inheritance for expansion. When all you have is ADTs, you _can't_ have inheritance, so you end up using composition (which is generally considered better design) and you are more likely to rely on the creation of "new-type" container types for everything. You think of them as a way to describe what the data is, not how you interact with it.
Apologies if this is a bit hand-wavy, I'll try to write a more thoughtful answer at a later time.
[1]: https://medium.com/javascript-scene/the-forgotten-history-of...
Also, ECS design is a thing in game development.
Are ADTs taken these concepts into language design?
Also, I don't mean Rust is harder than C++ or Java. It's just quite different.
If you are used to visualizing software as graphs of functions or classes in your mind for years, switching to the Rust model is quite a change of thinking.
> Most of this difference is not related to lifetimes. Rust has patterns, traits, dyn, modules, declarative macros, procedural macros, derive, associated types, annotations, cfg, cargo features, turbofish, autoderefencing, deref coercion etc
Nobody forces beginners to write macros. Beginners are only macros _users_. With time and experience, the need for macros emerges by itself, then learning them is a natural part of the process. But even then, nobody is forced to write any.
Dyn is also a very obvious concept to anybody who knows a bit of OO-programming in lower-level languages (e.g. C++). The choice to make dynamic dispatching explicit is arguable, but ultimately, although making it explicit is (AFAIK) Rust-specific, the concept itself isn't.
Complaining on pattern matching? C'mon :-) It's a bit like a Python programmer complaining that Golang has a switch/case.
I don't argue that Rust is hard or not, but it seems to me that the author was overwhelmed, and complained about everything, even simple things.
In my experience, in the Rust learning process (and programming experience), all the concepts above are dwarfed by the headaches induced by the borrow checker.
> Zig has it's own implementation of standard OS APIs which means that linking libc is completely optional. Among other things, this means that zig can generate very small binaries which might give it an edge for wasm where download/startup times matter a lot.
I'm curious about the details of this. Rust has `no_std`, however, it seems that in Zig, this is more (in a way) granular?