DEV Community

Sam Pagenkopf
Sam Pagenkopf

Posted on

Rust Module Essentials

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();
}
Enter fullscreen mode Exit fullscreen mode

Second, as a separate file:

// main.rs

mod food;

fn main() {
    food::eat();
}

// food.rs or food/mod.rs

pub fn eat() {
    println!("Yum.");
}
Enter fullscreen mode Exit fullscreen mode

So more specifically:

mod X means: let there be a module X, defined either here, in braces, or in a separate file named X.rs or X/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();
}
Enter fullscreen mode Exit fullscreen mode

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.");
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

Or even:

mod food;
use food::Meal::fancy;

fn main() {
    let lunch = fancy();
    lunch.eat();
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}

Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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};
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
thermatix profile image
Martin Becker

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.

Collapse
 
zinsbill profile image
zinsbill

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?

Collapse
 
hertz4 profile image
Sam Pagenkopf
// main.rs
mod back_of_restaurant;

// front_of_restaurant
use back_of_restaurant;
Collapse
 
zinsbill profile image
zinsbill

name the file either back_of_restaurant.rs or back_of_restaurant/mod.rs inside the directory "src/front_of_restaurant"

Collapse
 
moonboots233 profile image
Yao Adzaku

Sam, thanks for this overview of Rust modules.

Very helpful!

Collapse
 
emilohlsson profile image
EmilOhlsson

Really good summary on how the modules work!

Collapse
 
hertz4 profile image
Sam Pagenkopf

This is the first article on programming I've ever written, so give me any feedback that comes to mind.