Rust Module Essentials

hertz4

Sam Pagenkopf

Posted on December 19, 2017

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.

💖 💪 🙅 🚩
hertz4
Sam Pagenkopf

Posted on December 19, 2017

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Rust Module Essentials
rust Rust Module Essentials

December 19, 2017