The mod
statement based mounting system for modules is a desirable feature for many people writing lower-level code, because it allows developers to write #[cfg] mod
and similar, conditionally including (and excluding) entire file/module trees from inclusion in the compilation.
Similarly, the capability to define a type in a different namespace from where it's exported to the outside world is an important option in Rust, because of how privacy is used to encapsulate unsafe
ty. Without the ability to introduce an extra API-invisible privacy barrier, my structure's private state is going to be accessible by more code, and I have to trust significantly more code to maintain my invariants.
And even if there's no unsafe
anywhere in the crate, safe invariants benefit just the same, and being able to split up file hierarchy based on implementation rather than exported interface, having actual private namespaces, is extremely useful. Yes, it can sometimes be a bit nonobvious where some type/function is defined, but 99% of the time you can go from the export position (which is filesystem-tied) and trace reëxports back to the definition site; glob reëxports are generally discouraged outside of hyper-specific cases (e.g. bevy's prelude-of-preludes).
Java's organizational system is entirely conventional, by the way. There's no actual requirement that .java
source files match their package location on the filesystem; the package
declaration at the start of the file is all that matters. (Most tooling assumes this layout, but none of the core tooling requires it, IIRC. .class
files and the classpath lookup is fs dependent, and .jar
is just a fancy .zip
.)
There's also the fact that for Java, the unit of compilation is each individual .java
file, and the build system is in charge of compiling each .java
and packing together all of the individual .class
files created by that compilation. In Rust, however, the unit of compilation is the entire crate; the compiler rustc
gets the path to lib.rs
and discovers the rest of the crate via the mod
statement mounting points. The cargo
build system does very little for the local crate; its real work is in managing dependency crates.
The module/path system already underwent a significant migration from 2015 era Rust to edition 2018 and beyond. mod
mounting statements not inside an index module lib.rs
/mod.rs
/main.rs
; the extern crate namespace of ::lib
being a distinct namespace from crate::
being names at the root of the current crate.
Rust is kind of unique in that it has two ways of interacting with the module system: mod
to mount modules, and use
to bring names into scope from mounted modules. In scripting languages with similar relative-path-based import
, mounting and using a file are the same operation, and importing the same file more than once typically doesn't redefine its symbols. Rust on the other hand is perfectly happy to allow you to mount the same file more than once with mod
. (If we don't already have default diagnostics for when people do this, we absolutely should.) Private module paths are also somewhat unique of a concept to Rust, that I haven't seen elsewhere.
But Rust is also surprisingly good at noticing when you've made a mistake (e.g. use
d a path through a private module, use
d an unmounted module) and making suggestions as to what you wanted to write. Any specific cases where the compiler could reasonably do a better job of inferring intent absolutely should be tracked as issues; making the compiler more helpful on invalid code is one of the best superpowers of the Rust culture.
Would I make different decisions were I designing a new language from scratch today? Probably! But Rust's module system is established enough that it's not going to undergo any drastic adjustments. Especially not via some vague "do it better" ultimatum; at a minimum you would need to provide a draft of what you would want the new system to look like, what benefits it would bring, and what the migration path looks like. Even with that, though, the chance of making large changes (e.g. removing the need for mod
statements) is quite unlikely.
To control access to published modules there are many possibilities [...] it's just ridiculous the [...] restrictiveness of the module system...
This seems contradictory. Either there are a lot of (potentially redundant style-only) options, or the system is restrictive. You can't really claim that the module system gives you both too many and too little choice in how to express your API and implementation structure.
For what it's worth, the Rust system is the most expressive module/namespace system I've used. No other language has both proper module/namespacing-by-default support and allows you to export an API item independent from where it's defined. I don't feel any sort of restriction in what the language's namespace system allows me to express.