Revisiting Rust’s modules, part 2


#90

More awkward for the implementer, or for the user? I am talking about what’s the better UX for the user, which is more important. Regardless, I don’t see how it could be significantly complicated to implement given the explicit nature of Rust (dependencies are explicitly listed in Cargo.toml, in particular) since an IDE needs to support “go to symbol” and similar functionality anyway.


#91

Oh, okay, that makes a lot more sense.

I wanted you to imagine that cargo or similar was in use for the “top-level build” but the one-off program was compiled with a direct manual invocation of rustc. In the hypothetical, I have a shell window open with current working directory inside the source tree, and anything more than vi test.rsrustc test.rs./test is too much ceremony. And cd ../../.. or opening another shell window is Right Out, because then I will forget where I was and what I was doing.


#92

It also reads well enough for traits, though it gets a bit odd with nesting:

impl fmt::Debug from std for MyType { .. }
impl MyTrait for boxed::Box<error::Error from std> from std { .. }

since the name of the symbol boxed::Box can end up quite far apart from its source std.

I would also think this could make it tough to have macro_rules! understand and construct paths.


#93

I would nest the second example as the following, which reads slightly better:

impl MyTrait for (boxed::Box from std)<error::Error from std> { … }

On the other hand, as I said, awkwardness in this case is not a problem in code, as it nudges people towards use, but it might be good to have the compiler say something like:

The trait (fmt::Debug from std) is not implemented for the type boxed::Box<error::Error>
    using:
      - boxed from std
      - error from std

Rather than the less nice

The trait (fmt::Debug from std) is not implemented for the type (boxed::Box from std)<error::Error from std>

#94

More awkward for the user. Consider from/use and use/from.

In from/use, typing “from” we have context we want a module next. Then “use” now the next completion we can down select to a list of exports of that module. No backtracking, and one step leads naturally to the next.

In use/from, “use” then the completion list is all possible exports from all modules, a potentially large list, then “from” then a completion we can potentially automatically filled out if we know it, though things like wild cards might make this tricky.

Of the two using the module to give you a list of exports lets you explore more easily, and has a generally nicer flow for most folks.


#95

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?

[Pre-RFC] Yet another take on modules
#96

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.rsrustc 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.


#97

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.


#98

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.


#99

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.


#100

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.


#101

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.


#102

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


#103

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.


#104

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.


#105

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


#106

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…


#107

Here’s the final accepted proposal:


#108

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


#109

@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.