Do you remember the first time you made a project with more than one source file? Was it with headers, with classes...or, with modules?
Modules have certain traits. They allow various items to coexist in a unit, without being bound to a class. A module file generally has its interface (outward-facing public parts) on top, and its implementation (dirty inner details) on the bottom.
Understanding Rust's modules comes from understanding the keywords mod
, pub
, and use
. To state them simply:
-
mod
declares a module. -
pub
exposes. -
use
pulls things in.
Let's begin with an example. Modules can be defined two ways. First in place:
// main.rs
mod food {
pub fn eat() {
println!("Good.");
}
}
fn main() {
food::eat();
}
Second, as a separate file:
// main.rs
mod food;
fn main() {
food::eat();
}
// food.rs or food/mod.rs
pub fn eat() {
println!("Yum.");
}
So more specifically:
mod X
means: let there be a module X, defined either here, in braces, or in a separate file namedX.rs
orX/mod.rs
.
pub fn eat
makes eat
visible to main. Without pub
, main would not be able to call eat
.
Note that without mod food
in main, Rust would ignore food.rs
entirely.
Next, let's expand the above to something more deeply nested:
// main.rs
mod food {
pub mod lunch {
pub fn eat() {
println!("Take a break.");
}
}
}
fn main() {
food::lunch::eat();
}
Or, as multiple files. Note the use of mod.rs
, a special filename:
// main.rs
mod food;
fn main() {
food::lunch::eat();
}
// food/mod.rs
pub mod lunch;
// food/lunch.rs
pub fn eat() {
println!("Hamburger.");
}
Does this look like you expected? Notice how both fn eat
and mod lunch
are made pub
. They must be carried up this way, one level at a time.
And of course, anything without pub
is invisible outside its module.
Perhaps surprisingly, pub
treats struct members and methods on an individual basis.
// food.rs
struct Meal {
pub taste: String,
price: u32, // cannot see price
}
impl Meal {
pub fn eat(&self) {
println!("I taste {}.", self.taste);
}
fn purchase(&self) { // cannot call purchase directly
println!("Spent ${}. Ouch.", self.price);
}
}
Having a private variable price
means that you cannot make a Meal
outside of food
. A constructor method can help with that.
// food.rs
impl Meal {
pub fn fancy() -> Self {
Meal {
taste: String::from("miracles on plate"),
price: 44100,
}
}
}
// main.rs
mod food;
fn main() {
let lunch = food::Meal::fancy();
lunch.eat();
}
So, to recap:
pub
exposes an item, either on module or struct level, to its surrounding level.
Do note that Meal::fancy
could also be named Meal::new
, or even other things.
Next, let's examine use
to complete our set of three. use
is a tool that pulls in words to reduce visual clutter and finger work.
mod food;
use food::Meal;
fn main() {
let lunch = Meal::fancy();
lunch.eat();
}
Or even:
mod food;
use food::Meal::fancy;
fn main() {
let lunch = fancy();
lunch.eat();
}
Pulling in fancy
directly is an extreme example, but it does work. In the same way that pub
can apply to structs, so can use
.
A wrinkle with use
is that it has absolute paths. To understand paths, consider this non-working example:
mod food {
fn cheaper(a: &Meal, b: &Meal) -> u32 {
std::cmp::min(a.price, b.price)
}
}
This fails because std::cmp
is the same as writing self::std::cmp
, self
here being food
. It's a relative path. So, let's use an absolute path by adding ::
to the start.
mod food {
fn cheaper(a: &Meal, b: &Meal) -> u32 {
::std::cmp::min(a.price, b.price)
}
}
This works, but the use
keyword is also an absolute path, and can reduce typing in the long run.
mod food {
use std;
fn cheaper(a: &Meal, b: &Meal) -> u32 {
std::cmp::min(a.cost, b.cost)
}
}
Here's another way of doing it. Yes, use
does respect its scope.
mod food {
fn cheaper(a: &Meal, b: &Meal) -> u32 {
use std::cmp::min;
min(a.cost, b.cost)
}
}
There is some light alternate syntax for use
as well, in order to pull in multiple things.
mod food {
pub mod bread {
pub struct Slice {}
pub struct Loaf {}
}
pub struct Plate {}
pub struct Napkin {}
}
use food::bread::*; // pulls in Slice and Loaf
use food::{Plate, Napkin};
So, to be specific:
use
brings module or struct items into the current scope from an absolute path.
Also note that pub use
can be used as a flattening method.
mod food {
pub mod breakfast {
pub mod cereal {
pub fn eat() {
println!("Snip, crankle, porp.");
}
}
}
pub use self::breakfast::cereal;
}
fn main() {
food::cereal::eat();
}
This enables you to divide into further modules for organization, without adding a burden to the outside world.
Overall, it is better to be more explicit (avoid too much *
) and use
judiciously. It's better to know where things are coming from when you see them later, but added visual clutter and typing are no fun.
To recap:
-
mod
declares a module. -
pub
exposes an item by a single level. -
use
brings things from an absolute path to the current scope.
There is more, but I hope I have helped get things started. Let me know if you're interested in a second part.
Top comments (7)
Thank you, this has been super helpful, one of the biggest troubles coming from a ruby background was understanding how file structure and modules and such work in a language like ruby. Because in ruby the file and file hierarchy isn't part of the code structure where as in rust they are.
So again, thanks this is super useful for me.
I’m unable to understand how "mod" works. I've read chapt-7 from the book, read questions posted in various discussions and I’ve read tutorials. I can get mod to do simple, basic stuff, but nothing beyond the primitive examples supplied.
Similar to an example in the book, let’s say a project is divided in 2 directories:
src
└── front_of_restaurant
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
└── back_of_restaurant
├── cooking
│ ├── prepare_appetizers
│ └── cook_main_dishes
│ └── prepare_dessert
│ └── clean_up
└── management
├── order_supplies
├── assign_work
└── balance_accounts
What I feel the documentation does not cover is if, from take_payment, I need to call balance_accounts, located in a very different section of the code.
So I'm several levels down in front_of_restaurant and I want to call down into back_of_restaurant. In the calling code (in take_payment), I've tried the following:
mod back_of_restaurant;
pub mod back_of_restaurant;
mod src::back_of_restaurant;
use back_of_restaurant;
use std::back_of_restaurant;
mod ::back_of_restaurant;
mod crate::back_of_restaurant;
...and several other variations.
[Of course, I could also include the actual call itself, but the compiler never gets that far.]
All variations fail. Most result in:
^ no 'back_of_restaurant' in the root (or worse errors)
No 'back_of_restaurant' in the root? Looks to me like it’s there. I can code "mod back_of_restaurant" in main.rs up at the top (i.e., src dir) level and it works great. From main, starting at the top, I can run balance_accounts with no problem (after qualifying it properly). Why can this not be done from take_payment? I’ve coded "pub" in all legitimate locations, so I don’t think it’s a visibility issue.
I find this quite frustrating because much of the discussion printed in the documentation deals with "absolute paths". But if absolute paths were really in effect, I should be able to move a module to any arbitrary location and it would still find balance_accounts. Currently only main (top level) or "cousin" modules within back_of_restaurant succeed with the mod statement.
Can anyone enlighten me? If this is clearly explained somewhere, feel free to berate me and call me names (but just remember to tell me where the explanation can be found). If it’s not explained somewhere, then how did you figure out how mod works?
name the file either back_of_restaurant.rs or back_of_restaurant/mod.rs inside the directory "src/front_of_restaurant"
Sam, thanks for this overview of Rust modules.
Very helpful!
Really good summary on how the modules work!
This is the first article on programming I've ever written, so give me any feedback that comes to mind.