Modules
As the main unit of organization, a module can contain definitions of almost any kind, as well as other modules. It's also a way to gate access to items from the outside, but more on that later. One very important thing is how that plays together with having multiple source files. Each file in itself is a module already!
You add a submodule by writing a mod
statement inside the module you want to add it to. It comes on two flavors, either by providing a block after the identifier and defining the module in-place in the same file, or by terminating the statement after the identifier and therefore signaling Rust that you want to import a source file as the submodule.
When importing another file as a submodule, there's a strict convention to follow: The identifier of the submodule corresponds to the file name of it, excluding the .rs
file extension. Additionally, it has to be inside a folder named like the identifier of your current module, with the folder being in the same directory as your current one.
For example, consider this directory structure:
src
outer_module
inner_module.rs
outer_module.rs
main.rs
The file main.rs
is a special file, it maps to the crate root if your crate is an application. For libraries, that file is called lib.rs
.
Now the module of the outer_module.rs
file maps to crate::outer_module
, while the module of inner_module.rs
maps to crate::outer_module::inner_module
.
There is one exception to the file structure rules. If you want a module containing submodules to look a bit tidier, instead of having a code file and a folder of the same name in the same directory, you can define your module in a mod.rs
file inside the module folder. It will get its identifier from the folder name. It looks like this:
src
outer_module
inner_module.rs
mod.rs
main.rs
This maps to the exact same logical module tree as the file structure above. Just be warned that if you do this, you might find yourself with a bunch of open files named mod.rs
at one point.
To access an item inside a module (if you are allowed to access it, more on that in a bit), you can always refer to it by its full logical path, like you have just seen. You can also use use
statements to bring a module, or items inside it, into scope.
Of course, this can create name conflicts, which would trigger a compiler error, so you can use the as
keyword to import an item with an alias name.
Idiomatic Rust code mostly only imports modules, so you can use the items by referring to them through the module they are contained in. This avoids identifier paths getting too long, but you can still clearly see the module it comes from.
Now for another example, consider this as the code in main.rs
:
// Needed to put module into module tree
mod outer_module;
// We're going to assume a 'SomeStruct' struct without fields
// inside 'inner_module'
// Full absolute path
use crate::outer_module::inner_module::SomeStruct as AbsoluteStruct;
// Relative path, could also use 'self' in front
use outer_module::inner_module::SomeStruct as RelativeStruct;
// Full path to module containing a struct
use crate::outer_module::inner_module;
fn main() {
let full = crate::outer_module::inner_module::SomeStruct;
let absolute = AbsoluteStruct;
let relative = RelativeStruct;
let idiomatic = inner_module::SomeStruct;
}
Now this example only works if inner_module
and SomeStruct
are declared public. More on that now.