Note, the following post does not apply to edition = "2015"
, but rather, the 2018 edition and onwards
The idea, which has been witnessed to be counter-intuitive when starting, is that the file hierarchy does not exactly match the module hierarchy, especially at the "beginning" of the src/
directory.
I'm gonna try an alternative way of explaining it: imagine your src/main.rs
was actually an src.rs
file.
It represents the root of the code in your (binary) crate, and can have submodules defines within the src/
directory.
Then, a submodules would "always" a submodule.rs
, which can, in and of itself, have nested submodules:
src.rs
src/
printable.rs
printable/...
person.rs
person/...
In your case:
-
your printable
and person
submodules do not appear to feature submodules of their own;
-
src.rs
does not actually exist, it is, instead, named src/main.rs
(for a (directly executable) binary / an application), or named src/lib.rs
for a library.
With this, it should be now clear that src/main.rs
sits above the other two "files" / modules, which are "siblings" / of the same depth.
Another way of representing this file hierarchy is using the mod.rs
naming convention:
src/
mod.rs (in the case of src, it's actually main.rs or lib.rs)
printable/
mod.rs
some_nested_module/
...
person/
mod.rs
This view, although a bit more cumbersome, has the advantage of better mapping the Rust paths to the file paths:
-
the root of the paths is src/
, which contains the items defined in that "src
module" / crate root.
In rust, such root is written as crate::
We have, for instance, the items:
-
crate::main
, a function, defined in src/mod.rs
src/main.rs
-
crate::printable
, a module, i.e., something that can contain items inside: crate::printable::some_item
. Such items are defined in src/printable/mod.rs
(or src/printable.rs
for the other file naming convention).
-
crate::person
, anoter module.
Now, imagine, within such a file system, that you are inside the /printable/
directory, and that you want to refer to a file within the /person/
directory. If you used a non-qualified path, such as person/...
(the equivalent of use person::...
in Rust), that would be looking for a person
directory (module) nested within your current directory (current module). And you don't have a src/printable/person/...
file structure, so that would fail.
You have, on the other hand, two approaches to refer to the contents of that sibling-in-parent-dir directory:
-
either you use the ../person
path (in Rust: use super::person::...
),
-
or, since in this instance, the parent ..
(super
) is actually the root of the paths /
(crate::
), you can use that absolute path:
/person/...
(in Rust: crate::person::...
)
And when you are within the "src
module" = root-of-the-crate module (src/mod.rs
src/main.rs
file), you can still use both approaches:
-
either the relative path person/...
which leads to use person::...
in Rust;
-
or the absolute path /person/...
wich would lead, in Rust, to use crate::person::...
Since the former is shorter, we thus usually elide the unnecessary / redundant absolute path specified.
I hope this clarifies the pathing question. It turns out that it's the "ergonomic" shortcuts (naming src/mod.rs
as src/main.rs
and being allowed to use module_name.rs
instead of module_name/mod.rs
) which make visualizing the module hierarchy not obvious (e.g., src/{main,printable,person}.rs
being in the same directory).
Regarding:
This is because modules are not "auto-included", even if there are .rs
within the src/
directory: you have to "acknowledge" their existence by declaring them within the parent module contents, by using that mod modname;
line.
This is by design, to avoid "action at a distance" when dealing with a complex program structure and libraries:
-
Imagine you have a colleague working with some type, say, a foo: Foo
and calling foo.some_method()
on it.
-
Now imagine that you, on your own different module, define a new trait,
trait SomeTrait {
fn some_method (&self) ...
}
and decide that it would be convenient for you that Foo
s implement your helper trait:
impl SomeTrait for Foo { ... }
-
If the implemented traits weren't required to be in scope, you'd have introduced a method resolution ambiguity, or worse, shadowed an actual method call, within your colleague's code. If your colleague intended to call <Foo as path::to::SomeTrait>::some_method()
because they were aware of your code, then they can either use that fully-qualified method call, or they can use path::to::SomeTrait;
. Both ways show that your colleague was aware for the existence of SomeTrait
/ they have explicitly opted into using that part of your code, so there are no suprises this way