I’ve caught up on this thread now, and want to make a few scattered observations. In general I feel like we’re converging on a solid set of problems to focus on and ingredients to use, but haven’t quite found the ideal mix.
About relative paths: there’s a subtlety about use
-as-items at play here. Unqualified paths can be understood in two ways:
-
Based on what is in scope, which you might take to be a combination of items use
d in the module and those defined by the module (including submodules).
-
As a relative path starting from the current module, which means the start of the path must be an item the module defines.
These two views are the same in the current module system, precisely because use
declarations actually define items within the module. This is what makes self::
paths in use
match the behavior of unqualified paths within the module. Take the following example:
use std::io;
use self::io::Read; // possible because the above is an item
fn mention_write<T>()
where T: io::Write {} // possible due to either of the two views.
If use
ceases to define an item, we’ll lose this alignment, which is possibly worrisome.
This issue also has implications for the “use-universally” (i.e. “part 2”) proposal, because it’d let you do things like:
from std use io;
use io::Read;
which seems potentially pretty confusing. But if we don’t retain this use
-as-item behavior, then there would still be a difference between paths you can write in use
and those you can write in module bodies.
I suspect these considerations are at least part of why most languages employ absolute paths for import statements.
I wonder if there’s a way to (1) keep paths for use
starting at crate root, (2) keep use
as items, but (3) avoid the surprise when certain things work at crate root but not deeper within the crate.
Regarding export
. At first, I felt pretty resistent to adding this on top of our existing module system setup. And some others have voiced concerns about it providing a different interpretation of unqualified paths than use
statements. But I wanted to point out that it woud have the same interpretation as e.g. paths when writing a type in a function signature within a module. In other words, only use
would employ paths starting from crate root, and everything else would be based on what’s in scope.
I worry, though, that export
will end up being a point of significant confusion. As others have mentioned, it doesn’t actually guarantee that an item is visible outside of the crate. And it puts us back in a situation where it needs to be used to export certain things – submodules and use
d items – but not others.
One bit of hesitance about collecting data from existing crates: it seems likely that usage is influenced by our current defaults. I know I tend to avoid using self::
because it just feels weird. That said, some of the numbers on this thread are quite strong
I’d like to toss in my support for [std]
as a way of referencing external crates in paths in general. While I agree with @withoutboats that this isn’t as immediately obvious as the from
syntax, I think it’s easy to learn and will be encountered/taught very early on. I think it’s very helpful to have the same unambiguous path syntax work in all locations, and the [std]
syntax, like from
, helps the crate name stand visibly apart. I think we could also allow omitting a ::
when using braces to list multiple items:
use [std] {
io::{Read, Write},
collections::HashMap,
}
I also like the combination of this syntax with ::
for going to the current crate root; it feels very natural to then say that [std]::
takes you to the root of std
.