First: I think tackling and improving the ergonomics of the module system is a valuable goal, and there’s a lot I like about this proposal philosophically. The identification of specific patterns that are issues for large libraries is as far as I’m concerned, too.
However, I do have a couple reservations, and one of them is primarily about the experience of learning. In particular, I have a really serious concern along lines that @dikaiosune raised up-thread, based on my day-to-day experience outside Rust, in the day job. Work right now is a mix of JavaScript and C#, which have quite different module systems both from each other and from Rust. (This is a little long, but please bear with me, because it is going somewhere, and the context is illuminating.)
In JavaScript, files are modules,[1] which can export items or not at will, roughly analogous to Rust’s current system. In C#, namespaces, which are the thing most closely equivalent to Rust’s modules, are defined by declaring namespace TheNamespace.WhichMay.Be.Nested
and they’re open to extension: you can reopen them pretty much anywhere and add members to them. In JavaScript, as in Rust today, you have to import members from other modules explicitly and globs are allowed but culturally frowned upon.[2] In C#, you open a namespace and automatically have access to all of its public members, which is much closer to (though certainly not identical with) where this would land us—especially in one important area.
We have a pretty rapidly growing codebase, with new services popping up, but just our main monolith has about 8,000 C# files in it, which have ~7,800 namespace declarations, of which ~7,100 are unique.[3] I tend not to have the latest version of that monolith checked out in my normal work environment, because I mostly work on the JS on the front end. I do, however, semi-regularly review pull-requests that are part of that codebase.
And C#'s module system is awful for reviewing pull requests without pulling all the changes locally and doing a compare locally, and for that matter without being in my Windows VM with Visual Studio open—because it’s usually impossible (and that’s not an exaggeration) to be able to know where a given item came from without having Visual Studio open. The fact that I cannot see where an item came from—ever!—means it’s essentially impossible to learn how the pieces of the codebase fit together without having Visual Studio up and running.[4]
I’m a reasonably sharp guy. But as a result of this design decision for C#, I have found it incredibly difficult to get up to speed on this codebase, largely because, well… I don’t actually like having VS up and running unless I need it. And it seems ridiculous to say I need VS up and running just to take on the meaning of a 10-line change. It’d be a lot easier if I could just open a file and have some idea whether a given type is coming from our own codebase, from a third party library we use, or from ASP.NET MVC,[5] or from the C# standard library. Put another way: my experience of C# would be much more pleasant and have a much lower learning curve if it were possible to meaningfully navigate a codebase using VS Code or Vim in macOS.
By contrast, in modern JavaScript, and in current-day-Rust, if I want to figure out where something comes from… well, it’s normally pretty obvious. Because in both languages we tend to use glob imports relatively sparingly, I can usually search the top of the file for ::the_name
if it’s a bare item or ::the_namespace
if it’s the_namespace::the_name
.
What this highlights, to me, is that there’s a really important distinction to be made between learning the module system and learning a codebase via the module system.
We need to lower the learning curve of the module system itself. I’m 100% agreed about that. I’m also entirely agreed that it would be great to minimize some of the boilerplate and especially some of the duplicated boilerplate (use
and mod
and external crate
, oh my!). But whatever we do in that regard, we should be very careful not to increase the difficulty of just reading a codebase, because that’s an incredibly important part of what learning a language-and-its-libraries entails.
Unfortunately, the proposal here seems to me to very much increase this difficulty, precisely because it would push Rust’s module system much more toward implicitness and as a result dramatically lower the discoverability and navigability of the codebase at a plain-old-text level. That increase in cognitive load is something I think we should avoid while finding a way to improve both the learnability and the ergonomics of the module system itself.
Footnotes
-
modulo an important detail about whether the file includes export
.
-
When I do use them I still actively namespace them:
import * as SomeModule from 'some-module'
-
These are rough estimates: rg -t cs '^namespace .*$' | uniq | wc -l
.
-
It’s awesome when tools can help you by saving you time, but when you design a module system around the assumption that people will always have those tools available—and it’s quite clear that C# was designed in precisely that way—it means it can fall down horribly when those tools aren’t present.
-
Why Microsoft thinks everything has to be capitalized…