To restate these objections as I understand them:
- Its not great that
use uses different paths than those inside the module.
- Separating
use and export into two different keywords is worse than just having pub use work differently than use.
- Imports/exports should just use the absolute path syntax.
I think all of these objections are reasonable. I chose pretty opinionated default settings, and I didn’t clearly articulate the motivation for them.
I’ve experimented with applying this and the previous proposal to existing crates (mainly futures and chalk) and made these observations:
- Absolute paths are rare inside modules; inside modules you almost always want
self:: paths.
- Import statements (
use) are mostly from the crate root (crate::); many are also from extern crates (extern::); a small number are from this module or the parent (self:: and super::).
- Export statement (
pub use), in contrast, are mostly from this module (self::).
Based on this learning, I made these conclusions:
- Import statements should have the crate root as the default, and it should be easy (and hopefully self-explanatory) to import from other crates as well. It should be possible to import from anywhere.
- Export statements should have this module as the default. It should be possible to import from anywhere.
- It should be possible to use a path from anywhere inside a module, but it isn’t super important that it be the most abbreviated form.
And from these conclusions I think the design decisions follow:
The notion of path prefixes
Absolute paths have a prefix, which is one of:
-
self, refering to this module
-
super, refering to the parent module
-
crate, refering to the crate root
-
extern::<crate>, refering to a dependency
An absolute path, outside of an import/export statement, uses the syntax <prefix>::<path>.
use and export vs use and pub use
There are two motivations here:
- I wanted the default prefix for import to be different than the default prefix for export
- I wanted
use to not introduce items visible outside of this module, even in submodules.
Changing what it means to use foo::Bar by adding a visibility modifier seems very surprising to me. That use foo::Bar means to search from the crate root but pub use foo::Bar means to search from self is troubling.
Of course this is an argument against having different defaults here. Ralf made the case that all paths should have the same default prefix, whereas Josh made the case that import/export statement paths should not have a prefix specified at all. But I think, with two different keywords, having different default settings will be reasonable to learn.
The from syntax
There’s also objection to the from syntax as the way to change the prefix of a path in import/export statements. I want to point out that its actually shorter in the current form of this proposal for most cases. That is:
from std use io;
// vs
use extern::std::io;
In addition to being shorter, I think the first one is more intuitive.
Because absolute paths inside of modules are rare, I think users will learn the extern::std::io syntax later on, and will at first be using the from std. I would liken this to type ascription and turbofish. At first, I know that I can do:
let x: Vec<_> = iterator.collect();
But then I have a situation like this, where I don’t want to create a temporary:
s1.parse()? + s2.parse()?
At this point, I learn about the turbofish syntax:
s1.parse::<u32>? + s2.parse::<u32>()?
I would even say that they’re conceptually analogous - creating a temporary is like performing an import, whereas using an absolute path inside your code is like needing to inject the type into the middle of an expression with turbofish.
I might prefer supporting from as the only syntax for absolute paths also, though I don’t know how I (or anyone else) would feel about that:
let x = from std vec::Vec<i32> = vec![];