I have to admit that when I first heard criticisms of our module system, I was confused. âItâs so clear,â I thought, âit maps directly to the ASTâ. But over time Iâve kept those criticisms in the back of my mind, and Iâve come to see that there are some very confusing and suboptimal things about our module system. And also to realize that many users are not, in fact, compiler authors who think in terms of the AST. =)
To clarify, I am very much motivated by the fact that I have frequently heard that our module system is a stumbling block for beginners. But I am not solely motivated by this: I also believe that usability problems persist for experienced users â not as confusion, but as a continued source of papercuts. Some of them are rectified in this proposal, and some of them may indeed be improved.
I will list the things that I personally find confusing or annoying on a regular basis that I think will be improved:
- Forgetting to add a
mod declaration.
- For example, I commonly make a
foo/test.rs file then forget to add #[cfg(test)] mod test in foo/mod.rs. I only notice this when I expect a test to fail and it does not.
- Forgetting to remove a
mod declaration.
- I commonly rename or delete modules as part of refactoring. I do this in the file-system. I often update all uses even. But I often forget about the
mod declaration.
- The dance of adding an
extern crate dependency.
- When I realize I need something, I find it annoying to have to
- go to crates.io/crates and find the latest version
- go to cargo.toml and add it
- go to lib.rs and add it there
- add the use statement in the code that wanted it
- Admittedly, I could probably use
cargo edit, but I have never learned to and keep forgetting it exists.
- Forget the proper path to name something.
- I wasnât as aware of it until this discussion, but itâs true that I often find the file where something is, and then find out that this is not the proper name for it, and I find that a bit confusing.
- This happens sometimes in libstd, but mostly within my own crates, since for other crates I use
docs.rs rather than browsing their source directly.
- Under at least some versions of this proposal, within a crate I can always use the âtrue pathâ, though this is the part of the proposal Iâm least confident about. At minimum though it ought to make it easier to detect when a module is âinternalâ, though Iâm not sure that would help me, since usually I find things through ctags or ripgrep so I only have the filename where it is found and I havenât actually bothered to open the file.
These are papercuts for me, but they annoy me. But I think for many beginners they lead to much bigger frustration. Most people donât want to sit down and read a long tutorial, they want to get up-and-going hacking, so every bit of ceremony (make a file, now declare the mod, etc) stands in their way. Not being DRY also means that there are more things to copy and more things to get wrong.
There is one additional papercut that I think this proposal does NOT help. But which it may make it easier to explain:
- Confusion about which paths are absolute and which paths are global.
- I regularly try to type an absolute path in an expression (usually as a quick hack to test something) and find it doesnât work. I experience a moment of confusion (âbut I am importing
foo::baz above, why canât I use foo::bar?â). Then I remember I have to type ::foo::bar in expressions â for some reason this usually tops my âtoo ugly even for a hackâ threshold so I run up and add an import.
- The only way I think this might be easier to explain now is that one can say âuses are about bringing things from other files into your scopeâ. Ignoring âinline modulesâ for a moment, modules in this system can be thought of as being synonymous with files, and the idea that
use refers to âother filesâ and hence you have to write self::foo may be helpful as an explanatory tool. Or maybe not, I donât know.
Of what Iâve seen so far, I think there is one very real technical concern with the proposal (i.e., leaving aside for a moment questions of whether it is desirable):
- Case-insensitive file-systems.
- This is a solid point. In these systems, it is a pain to not know the desired case of the file you are looking for, and the way our name resolution works, we really want to know the names of the modules up front (i.e., without considering
use declarations). Bears some thought.
There are also some patterns that this proposal makes harder. Here are the ones I recall, did I miss anything?
- Overlaying multiple crates within one source directory (as we do with libs and binaries).
- Personally I think this is a very confusing pattern. It often works out ok because
main.rs is like a 3-line wrapper though. But itâs the recommended pattern today in many cases.
- Making temporary, throw-away
.rs files in your src directory.
- Iâll note that this is directly in tension with the usability problems I experience around forgetting to add
mod declarations. In fact, I make a point of keeping my src clean precisely so that git status is a useful tool to tell me what (A) I forgot to git add and (B) I forgot to create mod declarations for. In other words, so it can help me deal with the various bits of creating a module that are not DRY.
- Temporarily commenting out modules by commenting out the mod line.
- This can also, of course, be done by commenting out the contents.
With respect to @est31âs concern that they will constantly have to learn new things, I am sympathetic. This is a balancing act, and I think itâs worth asking ourselves, for every proposed change, whether it is worthwhile. But clearly we plan to make a number of ergonomic improvements that will involve changes in the recommended style of Rust programming. That is roughly the meaning of âstability without stagnationâ to me. Naturally, we want to ensure that old code continues to compile to the best of our ability, even if it is making use of deprecated forms, and that it is easy to adapt in any case; but we canât let that stop us from addressing known shortcomings. (Just in passing, Iâll note that my experience with C is quite variable here; old code frequently fails to compile, and when it does, it usually comes associated with an avalanche of warnings.)