I don't think "path confusion" is as simple as it is made out to be. That is, I think the underlying problem is not that import statements take an absolute path, and local statements take a relative path. In the previous thread, @theduke listed several minstream languages which use absolute paths for their import statements. I would add Ruby to this as well, though its UX is a bit worse because its require
-based.
Our problem is a bit different, and I think its got more to do with the shift from a single-file project to a multi-file project. I think in a single-file project, you get bad hints (from the everything is an item situation) that lead you down the wrong road.
For example, you might think that once you've added a dependency, you can use it everywhere. But the reason you can use it your main.rs file is that extern crate foo;
is an item, which adds "foo" to the namespace of the root. That's not an obvious fact to many users. Solution: we make all imports from external crates act the same way.
Another problem is that you write code like this once you add a submodule to your project:
mod foo;
use foo::Bar;
Now you have both foo
and Bar
in scope. You might think that because you declared mod foo;
, you can now use from foo. But this is only true in the root module, in other modules you have to say use self::foo::
.
A way to mitigate this is to only have imports and not mod statements. If you don't say mod foo
, maybe you don't build a mental model that you'll be able to say use foo
because you have that special "mod import" that you have to do when you add files.
I don't think this problem is entirely mitigated, and I would like for use foo
, where foo
is a submodule, to work in every module. I've had trouble finding a solution though, because of the problems that fallback creates with re-exports that pcwalton mentioned in the earlier thread.
However, I think path confusion is also mitigated to some extent by the from
syntax. That is, lets say your imports look like this:
from std use collections::HashMap;
from std use fmt;
from std use path::{PathBuf, Path};
from semver use Version;
from serde use ser;
use core::{Dependency, PackageId, Summary, SourceId, PackageIdSpec};
use core::WorkspaceConfig;
(These are the imports in cargo's core/manifest.rs
module.)
My belief is that by making from <crate>
a separate syntactic unit, users are hinted that the use core
statements have had a from
component elided. This makes it easier to internalize that bare use statements have the from crate
statement elided on them.
Similarly, I think that the from syntax also makes it easier to understand that use
and export
have different elided prefixes. "use = from crate
, export = from self
."