Revisiting Rust’s modules, part 2

I think this proposal is definitely much better than the previous one! So my part of feedback and some ideas (a bit random for now):

  • pub use is often referenced in terms of “exporting” something, so why not to call it just that? So we’ll write export foo; for exporting whole module to outside world, and export foo::{bar, zoo}; for exporting elements or sub-modules, and pub use will only be used for sharing inside crate. (of course export'ed items will be visible inside crate too) This way facade pattern can be expressed naturally using multi-line approach without much boilerplate and problem of “automatic promotion of key items” will be solved too. I think it certainly will help with teaching and intuitiveness of the module system. And maybe then even rename from/use to from/import for name coherence and to please Python crowd. :wink:
  • I think from/use will be better compared to use/from, as it’s represents top-down approach more intuitive for hierarchies. And in general I like removal of extern crate in favour of from/use and I think it has a nice readability compared to proposals based on use ::cratename... or use extern::cratename::.... Although we need to consider public re-exports and how they will look.
  • It would be good to allow from crate use self;, so items could be referenced as crate::foo. Or just copy Python and use from/import and import crate;. This way re-export will look as import crate; export crate;. Alternative would be pub import crate;, but I don’t like it much.
  • I personally feel explicit use's (or mods) for bringing module into the crate scope is a must have. One of the big negative points in the previous proposal was implicitness (i.e. taking module structure from file system), which I strongly dislike and which I think will have an undesired impact on code discoverability and readability. (yes, I am generally not using IDEs) Also warnings can significantly reduce value of mentioned potential problems. So in some sense use will replace current mod for files with only impl blocks. But at this point why not just leave mod as is?
  • Continuing on x/x.rs problem I think we can allow to write mod foo::bar::zoo for adding foo/bar/zoo.rs into crate scope without requiring mod.rses in the foo and bar directories and allow to write use zoo::item;, so we’ll forget exact path to zoo as an implementation detail.
  • About absolute/relative paths I am more or less indifferent, with slight favour of keeping absolute path to reduce breakage.
  • About pub(crate) by default, I personally don’t have crates large enough to have need for finer privacy granularity, so I can’t judge, but maybe need for it is an indication for time to split project into several crates?

Using only a single shell or editor window (unless you have a tiny monitor?) seems like a pretty niche workflow :slight_smile: but anyway, for running a one-off test program, what I do is vim ../../examples/xx_test.rs then cargo run --example xx_test which is no more ceremony than vi test.rs ... rustc test.rs ... ./test. I also have a .gitignore rule that ignores files starting with xx.

That said, would it work for you if the "files become modules" rule respected .gitignore? That's something that I think any version of this module proposal should take into account.

Instead, please imagine that there are 10+ shell and editor windows, each associated with different subdirectories or perhaps even different projects. Some of them may be ssh'd into remote machines. Keeping to a strict association of windows with specific locations is the only way I don't get hopelessly lost.

It's important for several other reasons that the test program's source code be in the same directory as the other stuff: It is often derived from the code in that directory, so I'm copying and pasting between the two and I want the editor to look in that directory, not ../../examples, when I use :e, :r or equivalent (note: I don't actually remember what vim does here); it may, when run, process code or data in that directory; it might not even be intended to run at all, just to be a bunch of notes-to-self associated with the code in that directory, but I still want to use the .rs extension for syntax highlighting convenience.

No, that would not be sufficient. The most important reason is that these files do get checked in under some circumstances, and may persist for quite some time. Another reason is that there may not be a version control system for something, or not one that the toolchain understands (does anyone here remember ClearCase?)

I don't want the behavior of the compiler ever to depend on the existence or state of version control. That may under some circumstances be reasonable behavior for cargo, but not for rustc.

At risk of getting heavily spammed… I’m still not sure what this proposal solves.

Yes, removing extern ... might make sense; I’m unsure myself. Some new privacy rule like pub(mod) is necessary.

And some IDE functionality to assist writing use ... declarations would help; that would probably be a better solution for beginners than switching to relative paths.

1 Like

While we are happily bikeshedding about variants of this in half a dozen threads, there is one thing from the first proposal which is absent in almost all other proposals, and which at least I personally liked a lot: Having files be anonymous modules. By this I mean that items in “bar/memory.rs” and “bar/cast.rs” all end up in the “bar” module directly, rather than in two separate submodules – while both files can still also have private items that are not accessible even to the other file. That is entirely orthogonal to the discussions around from ... use and path syntax and whatnot, so having these discussions separately makes perfect sense to me. However, it is not clear to me whether “files are anonymous modules” is tabled for now because there was significant pushback (was there?), or whether it is missing here because this is a separate discussion we are going to have after reaching some form of consensus on what is currently being discussed.

2 Likes

Perhaps you did not read my proposal carefully or maybe my wording was a bit unclear, but use(inline) (or mod(inline) if it will be decided to keep this keyword) expresses exactly your anonymous modules. In my personal opinion it’s a dangerous tool, due to its inherent implicitness and it must be used only if it’s possible to unambiguously infer from which file exported item is originated looking only on file names.

I've personally tabled the idea, in part because it would be a much more disruptive change to existing code. By contrast, the proposal in this thread, while requiring some breakage in the next checkpoint, has a very straightfoward, easily automatable fix.

In addition, there's the possibility of something like _foo.rs being an anonymous module (or also the inline modules thread). So we may be able to recover that idea in a less disruptive, more orthogonal way.

At the moment, my personal focus is to try to find a relatively conservative option that (1) solves some of the more prominent issues in the module system and (2) lays a good foundation for future work like anonymous modules.

2 Likes

Please do not adopt JS’s use/from; it’s grotesque. Adopt Python’s from/use.

3 Likes

I mostly like this proposal, except for deprecating mod. I would like to keep a way to have submodules defined in the parent modules file, or explicitly specify the path of a module. But I’m fine with inferring module structure from directory structure by default.

1 Like

I had several thoughts on (read: qualms about) the previous proposal.

My thoughts on this one are roughly: ship it. Lots of good discussion up thread about the trade offs but I think this neatly solves the specific teaching/learning model concerns I have with today’s module system as well as sidestepping those raised by the previous proposal.

Edit: except the no-private-modules part.

If modules are pub(crate) by default, then modules with _ e.g. _sub.rs can be used to make it private.

Is this discussion over? Where can I find the conclusion? It’s so sad to see that “directories as module” has been abandoned. I really desperately wanted it…

Here’s the final accepted proposal:

Things have advanced slightly since then, with a bunch of discussion in this thread: The Great Module Adventure Continues

The final version is described in this comment: The Great Module Adventure Continues

The tracking issue is here, with links to the PRs that have been merged so far: https://github.com/rust-lang/rust/issues/44660

2 Likes

@aturon

Speaking about trust to the RFC process, in this particular case:

  • There was a somewhat controversial RFC (not too much in the last version), that RFC was accepted.
  • Then there was a decision to change the rules significantly from the accepted version (extern prelude, standard crates in the prelude, all that).
  • That decision is hidden somewhere deep in the internals thread and never went through RFC process and community feedback.

To clarify, I more or less like where import/crate use went in the result (except for the edition breakage, to which I'm partly to blame because I didn't have enough time to implement the import resolution fallback), just pointing to the process issue.

I think even mini-RFC updating the formal text in place and "synchronizing RFCs and reality" would be a big improvement.

10 Likes

Ah right, I was looking at the date on the proposal and things weren’t quite adding up with when I remembered discussions happening, but it said “merged” and had an open implementation issue, so I went with it. I guess the key change in the target syntax is adding external crates to prelude so you don’t have to use them, or prefix with :: in items unless they are locally shadowed. As far as transition, am I correct that crate-relative use paths without crate:: are being deprecated much harder than they were before? Anything I’m missing?

It seems final proposal is talking only about crate and path stuffs. When I see some articles, there was intention to remove implicit module definition by file, but it seems disappeared now. Is that declined or just deferred?

The change being pursued as far as how the file system relates to the module structure is more conservative now. You still need explicit mod declarations, but you can put them in foo.rs instead of having to move your code to foo/mod.rs when you add submodules to foo.

It seems what I was thinking is slightly different.

My intention is making only directories to define modules, and files don’t. In this way, directory structure still reflect module structure, therefore, there’s no penalty on navigability. Actually this can provide better navigability by splitting one giant .rs file into multiple pieces (which shows actual structure better) without pub use everywhere or duplicated namespace/symbol names. For example,

qux
qux/bar
qux/bar/foo.rs

And foo.rs is

pub fn foo() {}

Then you get only qux::bar::foo() instead of qux::bar::foo::foo(). In my opinion, too much efforts needed to build this kind of module structure with pub use or just I have to everything in one big giant .rs file. In either way, really doesn’t help navigability.

As I don’t know well about history, I am not sure whether this kind of design already been addressed or not. Someone please let me know why if this design has been excluded.

Is it just me, but isn’t using the crate keyword in place of a module path make it ambiguous to the reader?

use crate::foo;
use something::bar;

The syntax of this makes it look like crate is an external module, not a keyword. Is this addressed currently?