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.
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.
I agree with you. It's a shame we don't have a simple visual metaphor to describe this
EDIT: Wait does hacker news just delete non-ascii? I tried to put an upside down smiley here how is this even worse than I thought
[1]: https://rust-lang.github.io/wg-async-foundations/vision.html
[2]: https://blog.rust-lang.org/2021/03/18/async-vision-doc.html
[3]: https://github.com/rust-lang/wg-async-foundations/pulls
[4]: https://github.com/rust-lang/wg-async-foundations/issues
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.
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.
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.)