Decoupled Module Improvements


#1

After my earlier proposal I had the idea that instead of a complete overhaul of semantics, there might be a solution in small, individual changes.

So with the recent discussions and pain points that have been brought up, I have reduced the proposal to two features and an inaction.

I should again point out that all of the ideas are taken from earlier proposals.

#![feature(structured_paths)]

Instead of the current path formats, the following will be recognized:

  • foo::bar (starting with an identifier) is relative to the current module.
  • ::foo::bar (starting with ::) is an absolute path in the current crate (see Options below for an alternative).
  • extern::cratename::foo (starting with extern::) is absolute in another crate.
  • super::foo and self::foo as relative prefixes are as they are today.

The extern:: path is regarded as external crate reference, meaning there is no need to use extern crate for it to work. However, the external crate is only available through extern:: and not as an item in the current crate root.

This feature is not backwards compatible. It would require a feature flag or an epoch-like facility to activate by default.

Options

  • ::foo::bar could be crate::foo::bar, but that is bikeshedding territory as they both would have the same semantics.
  • extern::cratename:: could have different syntax, which would also not change the semantics and is more a visual discussion.

#![feature(glob_modules)]

Keep mod working just the way it is currently, but allow the module name to be replaced with * causing all submodules of the current module to be loaded, except those loaded explicitly. Renaming is disallowed (for now) with the glob variant.

As an example, assuming the following files:

  • src/lib.rs
  • src/a.rs
  • src/b.rs
  • src/c/mod.rs
  • src/c/lower.rs

then the following statement at the crate root src/lib.rs:

pub(crate) mod *;
pub mod a;

will expand to:

pub(crate) mod b;
pub(crate) mod c;
pub mod a;

Note that it only pulls in direct submodules of the current module, and does not affect any lower submodules.

The rationale for requiring * for mod when extern crate is no longer necessary is that extern:: paths don’t introduce items in the module tree, but mod does. The * is semantically symmetric to the one in a glob import use foo::*. Both mark sources of items pulled in the current namespace that aren’t explicitly mentioned.

This feature is backwards compatible and can be enabled by default.

The extern crate situation.

The structured_paths feature above means there is no need for an explicit extern crate foo if the path extern::foo is accessed anywhere.

However, I would propose to leave the extern crate as it is right now and not deprecate it because:

  • It can still be used to import a crate root as an item into the current module.
  • Having it only be an import of a crate root is backwards compatible.
  • Existing documentation and examples continue to work in providing a local symbol where extern crate is used.
  • It doesn’t have to be deprecated now and a separate discussion can be had if it should get hard-removed in future epochs, or linted, or kept.

Edit: Added extern::cratename:: to Options as visual discussion topic and fixed some prose. Adjusted extern crate section to mention possible future deprecation. Thanks to @RalfJung!


My Preferred Module System (a fusion of earlier proposals)
My Preferred Module System (a fusion of earlier proposals)
#2

The latter here is self and super, I assume you mean extern::cratename::foo.

I think extern crate should be deprecated – if only with the goal of pushing people to the new system so that code is more uniform and easier to understand for newcomers. Re-exporting an entire crate inside the current crate should be done deliberately, not “just because extern crate is still in the code and this is how things used to work”.

In @withoutboats’s thread, someone mentioned that extern::cratename::foo is a little itchy, and I agree with that. The crate name is not really an item name, yet it appears in a place where usually there would be item names. So, IMHO the Options subsection should be extended to not just bikeshed about ::foo vs crate::foo but also about extern::cratename::foo vs something else. But again that’s just syntax, the semantics stay the same.

Other than that, there’s nothing here I object. :wink: I don’t have an opinion on mod * vs. just loading from the file system. Similarly, you make use stay an item like it is now, which I prefer – but making it just extend the search space for this module (and having some new thing for re-exporting) would also be fine for me. The only thing I really care about is consistency of the way paths are interpreted, and (distant second) consistency in general, e.g. around use and pub use (in case pub use is still a thing).

Btw, one way to make structured_paths backwards compatible is to introduce a new keyword – maybe import rather than use or so. But now I’m getting way ahead of the discussion, already thinking about a transition path :wink:


#3

Thanks, yes. I fixed that.

That’s certainly possible. I do like that in this scenario, that is a separate discussion. Could be kept for backwards compatibility but with a lint proposing use extern::cratename or removing it entirely instead.

You’re right, I added a note to Options to reflect that.

Yeah, I largely agree on priorities. Of course I’m biased with regards to mod * :slight_smile:

Possible, but that would complicate using the paths outside of import statements, since you’d need to disambiguate there too.


#4

How that? Nothing changes outside of import statements. Things stay relative. Maybe ::foo gets deprecated in favor of crate::foo, but the rest is just adding new things. Only when people remove extern crate and replace it by use extern::, they have to also change things eslewhere in their code, but that’s fine from a backcompat standpoint.


#5

Reading it again, I should clarify: extern crate can certainly be deprecated under this scheme without problems. But since their only leftover capacity would be a fancier use extern::cratename that decision can stand on its own.

The reasons for not deprecating it are more reasons for not deprecating it within the scope of this proposal.


#6

I’m not sure I understand, under this proposal foo would be relative to the current module, while it is currently looking for ::foo as well. So you’d have to use extern::std::io (or the equivalent) instead of the current use std::io. And for other things you’d need a way to do impl extern::std::Display for ...


#7

Note that you can import a crate root as an item even without extern crate, use do use extern::std;.

So, besides backwards compatibility, I don’t see any argument for keeping extern crate. Which is exactly the kind of thing that should be deprecated. :slight_smile:

under this proposal foo would be relative to the current module, while it is currently looking for ::foo as well.

…?

you’d have to use extern::std::io (or the equivalent) instead of the current use std::io.

Right, which is why I suggested a new keyword. The only currently valid code that changes its meaning under this proposal is use with a path not starting with ::, self or super.

That’s backwards compatible.


#8

I just realized my confusion: I completely forgot that the absolute resolution only applies to use statements, since I always import the namespaces of things I refer to.

So yes, you’re fully correct. Having a new keyword instead of use would make it backwards-compatible. Thanks for the reminder.


#9

The same argument applies to self::, right?

I’m in favour of the semantics of #![feature(structured_paths)] with the syntax:

use ::something_in_crate_root;
use [std]::fmt;  // instead of extern::std::fmt

Since [other_crate]:: can be used everywhere and is visually concise, I would never import a crate root.


#11

We’ll need a motivation section for #![feature(structured_paths)] which overcomes the objections raised. So far I’ve seen:

  • Other languages use absolute paths in imports, so doing otherwise would be surprising.
  • Numbers show self:: is relatively rare, so requiring a leading :: in use is burdensome.
  • Leading :: in use is ugly.
  • Absolute in use and relative elsewhere is an easy rule to remember, so that’s not contributing to “path confusion.”