Yet another module modification proposal


Hi, i’ve been reading several articles about new module design by aturon and withoutboats. Those ideas looks great at their own rights. However i just don’t feel it’s appealing enough to make such a move (i.e. kill old pros together with the cons). I’d like to write down what i’m thinking to see whether other people think one or more of them make sense.

  1. deprecate extern crate foo.
  2. [pub] mod *; for module discovery at current level, and [pub] mod foo; for loading only
  3. See anything starting with :: as absolute, reserve ::self to refer to current crate.
  4. Keep current module visibility rules (See them are restrictions on their items). However, no longer distinguish empty mod and non-existent mod and invisible mod in casual paths. i.e. use self::empty_mod::*, use self::inexist_mod::*, use self::empty_mod, use self::inexist_mod all does nothing and doesn’t raise an error, whether pub or not.
  5. allow pub use ::self::**::{foo::Foo, bar::Bar, module::inner_module} which searches and reexport these types or traits or modules.
  6. No changes to pub(restricted) and friends. Just read pub keyword as “publicity”.


I’m not sure how this helps the learnability issues that originally motivated the module system changes to begin with.


This removes some boilerplate, but the main motivation was:

  • both mod an use create items that can conflict with each other, and no other language has equivalent of mod, so it’s very weird and surprising to new users.
  • In crate root all paths appear to be absolute, but in modules they diverge — paths in code suddenly seem to be relative. This again is confusing and feels inconsistent to new users, and makes learning curve steeper.


namespace in C++ goddamit.
mod is a namespace that can be optionally “outlined” in a separate file in a slightly less verbose way than in C++.
Many things in the module system are made to look familiar to C/C++ developers (the primary Rust audience) - namespace == mod, use == using, same mod separator in paths ::.


namespace is not at all equivalent to mod. It is equivalent to mod .. {}, but it does not trigger or gate the inclusion of a separate file, which is the part that the RFC removes.


mod m; is an additional convenience to avoid separately includeing the file containing mod m { ... } (like it’s done in C++), it changes nothing fundamentally, “not at all equivalent” is as far from the truth as possible.


I see the similarity. However, there are crucial differences:

  • The declaration is in the file, not outside of the file.
  • It doesn’t control files. It’s not related to build system at all.
  • It can be repeated in multiple files, so can be seen more as a name-prefixing macro (opposite of use).
  • Compilation units, headers, and some attempts at actual modules make comparisons all muddled.


There are more crucial differences!
For example, names from outer namespaces are in scope in inner namespaces, privacy in C++ doesn’t work with namespaces but with classes. (The “declaration is in the file, not outside of the file” doesn’t seem crucial to me though.)
But knowing namespaces and includes still provide an excellent starting point into Rust module system because it can be initially explained in these terms. The large scale project structure can be expressed almost identically with "namespace + include + using" and "mod and use" (using is an “item” in C++ too, btw), all that remains is to learn differences in details.


The point remains that no other language has an equivalent to the way Rust decides to include files in the build, and that’s the thing that’s weird and surprising.



The point remains that no other language has an equivalent to the way Rust decides to include files in the build, and that’s the thing that’s weird and surprising.

I don’t see “not doing exactly the same as other languages do” as a cons at all. I think writing all things down (explicitly) and to let compiler help you check them is exactly the spirit of the Rust Style.


It’s not just “we should match other languages” by itself. It’s that the module system, of all things, is not something that needs to be different from other languages, because in this particular case that makes it harder for no real gain.

What exactly does the compiler check for you in the current module system that it can’t in the proposed system?


In this case, nope in checking. However implicitly load all files in work tree as modules is:

  1. Forbidding people to put random files in working tree.
  2. Putting an restriction on all external tools that they can’t generate anything with .rs unless intended.
  3. Allowing to inject code as module very easily. (Which is both good and bad).
  4. No longer allow include! an .rs file (have to use another suffix) since it will be loaded twice.

No, i’m not saying the proposal is bad. It’s just that changing existing users’ habit is … I’m not sure it’s worth it at all. That’s why i suggest using mod *; to opt in this behavior.


… easily worked around using the #![ignore] attribute in the current RFC.

(Although personally I’d prefer a .rustignore file similar to .gitignore/.npmignore, so that tools such as @BurntSushi’s ripgrep, which already know how to use .gitignore, don’t have to learn how to parse rust’s # directives to do their job.)