I'm curious, not saying you're right or wrong: what bits do you think are avoidable?
Personally, I agree that Rust is complex, but almost all of that complexity is inherent to the kinds of tradeoffs Rust makes. With different tradeoffs, I can certainly imagine a much simpler language, but it's less clear to me what stuff could be significantly simplified without doing so. However, I am extremely biased!
1. 90% of the time, rust module layouts are basically a copy of the file-system heirarchy, so why do I have to type this? This system would be much more approachable if it gave the file-system hierarchy as the default module layout, and let you override it explicitly. The argument I have heard against this is that some people like to comment out module declarations during debugging. I don't find this compelling, because I don't see why you couldn't have an explicit way to ignore a module instead. This is just one of many cases where Rust seems to prioritize the edge case at the expense of the common case.
2. On top of requiring a lot of boilerplate the module system is quite esoteric. I've had to learn it twice: a few years ago I was dabbling in Rust, and I remember having to struggle a bit to understand how to add a second file to my project, and then about a year ago when I was getting back into rust I found it unintuitive a second time. I challenge you to find someone who is unfamiliar with the module system, and see how long it takes them, using only the documentation, to figure out where they have to put the `mod` declarations to add nested submodules to their project to make it compile. Maybe I am unreasonably thick, but I don't think it's my problem since I've worked with a lot of languages, and never had this much trouble.
3. This esoteric system isn't even deterministic. Imagine I have the line `mod foo` in my `lib.rs`. Where can I go to find the source for that? Well, it depends. It could be in `src/foo.rs`, or it could be in `src/foo/mod.rs`. And let's say I'm using external crate: `use some_crate`. Which import does that correspond to? It could be: `some_crate`, or it could be `some-crate` in my `Cargo.toml`. You just have to kind of know all of these implicit behaviors of the compiler to know what's going on.
So this is just one feature, but IMO it's just one example of a case where Rust puts very little emphasis on the UX and understandability of the language.
Cool thanks! This is a great example of my mental bias, actually: I don't tend to think of the module system as a "language feature" even though it clearly is!
There are some good reasons and interesting arguments around all this, but given that I was just curious about your opinion, I won't bore you with all that :) Thanks!
I definitely think you're right that the module system is complicated, even the new module system. I would like to make one nit though: it's definitely deterministic. Deterministic just means it can be predicted, not that you specifically can predict it. If the module system was non-deterministic that would be a much bigger issue
Ok fair enough, that is true. I guess it would be more accurate to say that a module declaration is ambiguous with respect to the file-system location of the code it's referring to.
I'll chime in and say that for me at least, the module system was a hurdle at the start and coincidentally the only part of the language where the explanation in The Book (which is excellent, so thank you, btw!) did not click for me.
I think it's debatable whether the module system really is complex or just different from what newcomers are used to (and by now I've grown pretty accustomed to it). But in contrast to most other language features, where it was clear what I was getting in return for the steep learning curve, the module system seemed overly complicated at the time for no real benefit. Not a big issue by any means and I would choose Rust with its module system over the alternatives most days of the week, but it is one tradeoff that to me at least seemed orthogonal to the other borrowing-related complexities.
(What is more worrying to me nowadays is the whole async story. I do hope that some of it will get better once certain features land and it is certainly an area where some additional complexity is unavoidable, but it is the only part of Rust that I dread touching despite heavily using async in a moderately sized personal project due to the need for WASM + IndexedDB, simply because lifetime issues become much more tricky once async and either traits, recursion or closures are involved. By now I am consciously trying to limit any async parts of the program to a simple and stupid "Rust-light" style without any "fancy" features such as traits or closures, which does not feel like a proper solution. So yeah, in general I agree with you: Rust is certainly complex, but for the most part not unnecessarily so.)
Yeah, teaching the module system is kind of my white whale. Carol and I have spent more time on that part of the book than almost any other; re-written like five times.
My current working theory is that most people assume that "the module system" is similar to whatever one they've used in the past, then run into problems, and leads to frustration. I've talked to so many people who have totally opposite problems with it, with no real pattern to issues or expectations.
I think that it's very straightforward, personally, with very very simple rules (especially in 2018). But I certainly acknowledge that I am the exception, not the rule.
> My current working theory is that most people assume that "the module system" is similar to whatever one they've used in the past, then run into problems, and leads to frustration. I've talked to so many people who have totally opposite problems with it, with no real pattern to issues or expectations.
When I treated modules as Java packages or C# namespaces I hated them. Only when I realized that I can treat them as glorified C imports did they start to make sense. I still hate them, but at least I can rationalize their design now.
The rules are mainly there to filter out emoji; it can do most non-ASCII outside of that. L͇̭̝̲͍̠̼͒̌̋̄͟͝ͅi̢̭̬̥͍̣͓͗͆͆̄͝k̸̙̹̠̘̤̪͓̼̠̋̽̿̇̕͜͝͠ė̜͙̥̟͍͇͉͖͌́̑̈́̎͐ Ž̶̨̛͇͙̹͖̳̂͊͐̅̇̓a̸̭͙͇͙͙̝̟̯̮̠͌̊̑̏͊͡ĺ̸̻̟̩̼̙͈̒̿̀̈̓̌́̐̓ģ̵͉̺̹̥̤͎̟̓̓̑̋̈́̇͢͜͡ơ̷͖̤͎̮͈͆̌̿͂̂̚͘.̴̱̬̮̟͖͆̄̉̔̏͛̒̇̌ͅ
I don't know what "modern communication" you're using, but the only emoji I use are ones that can be written in simple ascii, :( , :-/ , :) , :D , ;) , etc. Most emoji are simply symbols for different activities, but they don't need to be used at all. The only place I see heavy use of emoji is on twitter, but that's because of the character limitation. Additionally emoji are colored and not black and white like text so they're glaring with respect to text around them.
You might be interested in taking a look at and potentially participating in the "Async Vision Document"[1] which is an exercise the team is going through to collect feedback about the current state of the ecosystem and what the pain points are, as well as a way to lay doing what the desired future state of async Rust should be[2]. The process is happening, as you would expect, in the open and there's still time to influence it[3] if your concerns aren't yet addressed or even mentioned[4].
> By now I am consciously trying to limit any async parts of the program to a simple and stupid "Rust-light" style without any "fancy" features such as traits or closures, which does not feel like a proper solution.
I disagree that this is bad, and I think you've made a good decision.
I find that too many Rust programmers reach for a closure WAY too often and, even when they should grab a closure, they make it far too complicated. Closures should be short. Inline closures are nice when they're a single line part of "collect()" (although, I've seen some that make me want to hang the author ...).
However, if your closure is 15 lines long invoking a builder chain (this is a common issue in EventLoop type closures), that should be a function. This is before I get started about how builder chains are a gigantic misfeature to paper over the fact that the language doesn't have default/named function arguments.
Anyway ... I have found that "make it a named function and call it" is often a far better way to communicate exactly what your intent was.
> builder chains are a gigantic misfeature to paper over the fact that the language doesn't have default/named function arguments.
Totally agree. I think it's telling that Rust Analyzer basically inserts argument labels inline in the editor, and this is the preferred way to work with Rust.
Personally, I agree that Rust is complex, but almost all of that complexity is inherent to the kinds of tradeoffs Rust makes. With different tradeoffs, I can certainly imagine a much simpler language, but it's less clear to me what stuff could be significantly simplified without doing so. However, I am extremely biased!