Modules and use to control scope and privacy

Rust has a feature that's often referred to as "the module system," but it encompasses a few more features than only modules. In this section, we'll talk about:

  • Modules, a way to control the privacy of paths
  • Paths, a way to name things
  • use a keyword to bring a path into scope
  • pub, a keyword to make things public
  • re-naming imports with as
  • Using external packages
  • Nested imports to clean up large import lists
  • "glob imports" with * to bring everything into scope
  • Splitting modules up into individual files

First up, modules. Here's an example of some code that uses modules:

mod foo {
    fn bar() {
        // code goes here
    }
}

fn main() {

}

As you can see, we've defined two functions, main and bar. The bar function, however, is inside of a mod block. This block defines a module named foo. You can nest modules inside of other modules:

mod branch1 {
    mod branch2 {
        mod branch4 {
            fn leaf() {
                // code goes here
            }
        }
    }

    mod branch3 {

    }
}

fn main() {

}

Remember in the last section, when we said that main.rs and lib.rs are considered "crate roots?" This is because the contents of either of these two files form a module named crate, at the root of the crate tree. So in this example, we have a module tree that looks like this:

crate
 └── branch1
     └── branch2
        └── branch4
     └── branch3

This might remind you of the filesystem you have on your computer; this is a very apt comparison! The module system is similar to a filesystem in many ways; analogies to filesystems are usually very helpful, and we'll be making them in this chapter.

Just like directories on a filesystem, you place code inside whichever module you'd like. How should you split up your code into modules? What should you name those modules? In order to talk about that, we need to learn about pub. But before we get to pub, let's talk about a seemingly simple question: how can we call the leaf function?

Paths for referring to something

If we want to call a function, we need to know its path. It's sort of a synonym for "name," but evokes that filesystem metaphor. Additionally, functions, structs, etc may have multiple paths that refer to the same place, so "name" feels slightly off.

A path can take two forms:

  • An absolute path starts with a crate name, or a literal crate, to refer to the crate it's in.
  • A relative path starts with self, super, or an identifier in the current module.
  • Both kinds of paths are followed by one or more identifiers, separated by double colons (::).

What's the path of leaf? Let's simplify our code a bit:

mod branch1 {
    mod branch2 {
        fn leaf() {
            // code goes here
        }
    }
}

fn main() {
    // how do we call leaf?
}

If we wanted to call leaf from main, we can do it two ways:

fn main() {
    // absolute path
    crate::branch1::branch2::leaf();

    // relative path
    branch1::branch2::leaf();
}

The former is an absolute path. Because leaf is defined in our crate, we use the crate keyword to start an absolute path, and then include each of the modules until we make our way to leaf. This is kind of like running /branch1/branch2/leaf as a program on your computer; the crate name is like starting the path with / in your shell.

The second one is a relative path; it starts with the name of branch1, a module that's at the same level of the module tree that we are. This is kind of like running branch1/branch2/leaf as a program on your computer; starting with a name means that the path is relative.

You may be thinking "wow, that's a long name. Look at how we had to repeat all of that branch1::branch2 stuff just to call leaf twice." You're not wrong. But before we can talk about how to simplify this example, we have a problem: this example does not compile!

> cargo build
   Compiling sampleproject v0.1.0 (file:///projects/sampleproject)
error[E0603]: module `branch2` is private
  --> src\main.rs:10:5
   |
10 |     crate::branch1::branch2::leaf();
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0603]: module `branch2` is private
  --> src\main.rs:12:5
   |
12 |     branch1::branch2::leaf();
   |     ^^^^^^^^^^^^^^^^^^^^^^

While we have the correct path for leaf, we cannot call it, as it's private. It's time to learn about pub!

pub to make paths public

Earlier, we talked about the syntax of modules, but we didn't really talk about why they exist. Modules are the privacy boundary in Rust. In other words, if you want to make something private, you put it in a module. Here's the privacy rules:

  • Everything is private by default.
  • You can use the pub keyword to make something public.
  • You are not allowed to use private code inside of children modules.
  • You are allowed to use any code inside of parent modules or the current module.

In other words, privacy works "down" the module tree, but is public "up" the tree. Again, think of a filesystem: if a directory is private, you cannot look into it, but you can look inside the current directory or any parent directories.

Our error said that branch2 was private. Let's fix that:

mod branch1 {
    pub mod branch2 {
        fn leaf() {
            // code goes here
        }
    }
}

fn main() {
    // absolute path
    crate::branch1::branch2::leaf();

    // relative path
    branch1::branch2::leaf();
}

Adding the pub keyword in front of mod branch2 makes the module public. This means that, if we're allowed to access branch1, we can access branch2. The contents of branch2 are still private; that is, making the module public does not make its contents public. It purely lets code in its parent refer to it.

We still have an error, though:

> cargo build
   Compiling sampleproject v0.1.0 (file:///projects/sampleproject)
error[E0603]: function `leaf` is private
  --> src\main.rs:10:5
   |
10 |     crate::branch1::branch2::leaf();
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0603]: function `leaf` is private
  --> src\main.rs:12:5
   |
12 |     branch1::branch2::leaf();
   |     ^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 2 previous errors

You can use pub on more than only modules; you can use it on structs, enums, and functions as well.

Let's make leaf public as well:

mod branch1 {
    pub mod branch2 {
        pub fn leaf() {
            // code goes here
        }
    }
}

fn main() {
    // absolute path
    crate::branch1::branch2::leaf();

    // relative path
    branch1::branch2::leaf();
}

This will now compile! Let's look at both paths and double check why this works.

In the absolute path case, we start with crate, the root of our crate. From there, we have branch1, and it is a module that exists. It's not public, but because we're in the same module as it's defined, we're allowed to refer to it. Next is branch2, which is pub, so that's fine. Finally, leaf, which is also pub, so we're good!

In the relative path case, it's the exact same, without the first step. branch1 is in the same module as us, so we're fine. branch2 and leaf are pub. Everything checks out!

You can also construct relative paths using super. This is like .. in a filesytem; that is, it says to start looking in the parent module, rather than the current module.

mod foo {
    fn bar() {
        super::baz();
    }
}

fn baz() {
    // code goes here
}

bar is in the foo module, so we can use super to go to its parent module, which in this case is crate, the root. From there, we look for baz, and find it. Success!

If you use pub on a struct, you can make the struct public, and also its members on a case-by-case basis:


# #![allow(unused_variables)]
#fn main() {
// this struct is public...
pub struct Point {
    // ... and so is x ...
    pub x: i32,
    // ... but y is private
    y: i32,
}
#}

If you make a public enum, all of its variants are public, so you only need the pub next to enum:


# #![allow(unused_variables)]
#fn main() {
pub enum ThisOrThat {
    This,
    That,
}
#}

There's one more way to use pub that we haven't covered, and that's using it along with our last module system feature: use.

use to bring paths into scope

If we look at our code, even though we only call leaf twice, there's a lot of duplication by specifying the whole path every time:

mod branch1 {
    pub mod branch2 {
        pub fn leaf() {
            // code goes here
        }
    }
}

fn main() {
    // here
    crate::branch1::branch2::leaf();

    // and here
    branch1::branch2::leaf();
}

We can use the use keyword to fix this:


mod branch1 {
    pub mod branch2 {
        pub fn leaf() {
            // code goes here
        }
    }
}

use crate::branch1::branch2;

fn main() {
    // we can now do this!
    branch2::leaf();

    // this still works too
    branch1::branch2::leaf();
}

If we say use and then a path, it's like creating a symlink in the filesystem. branch2 is now a valid name in this module, just like any other. We can now reach it through the older, full paths, or this new path that we've created with use. use also checks privacy, like any other path.

If you want to use use with a relative path, there's a small wart: instead of being able to use a name in the current scope, you must prefix it with self:

use self::branch1::branch2;

This may not be neccesary in the future, but it's something to keep in mind currently. Your authors rarely use self, preferring to always use crate and absolute paths. This way, when you move code around, the imports it needs don't change. Up to you!

A brief note about idioms:

// idiomatic import
use crate::branch1::branch2;

// idiomatic call
branch2::leaf();

// unidiomatic import
use crate::branch1::branch2::leaf;

// unidiomatic call
leaf();

For functions, it's considered idiomatic to use the parent module, and use it to call the function that way. This makes it clear that it's not locally defined, while still minimizing boilerplate.

For structs, enums, and other things, importing them directly is idiomatic For example:

// idiomatic
use std::collections::HashMap;
let map = HashMap::new();

// not idiomatic
use std::collections;
let map = collections::HashMap::new();

The exception is if the names would clash:

use std::fmt;
use std::io;

fn foo() -> fmt::Result<()> {
fn foo() -> io::Result<()> {

We couldn't bring both Results into the same scope, or their names would clash.

Making an import public with pub use

When you use something, it brings that name into scope, but it's private. If you want it to be public, you can combine pub and use:

mod branch1 {
    use self::branch2::leaf;

    mod branch2 {
        pub fn leaf() {
            // code goes here
        }
    }
}

// this won't work
use branch1::leaf;

Here, while we can access branch1 because it's in the same module, and leaf does exist inside of branch1 thanks to use, it's private.

If we change it to pub use self::branch2::leaf, it would now be public and that line works!

pub use is sometimes nicknamed a "re-export", since you're both bringing something into scope, but also making it available for others to bring into their scope.

Re-naming imports with as

Speaking of clashing names, we could solve this another way:

use std::fmt::Result as FmtResult;
use std::io::Result as IoResult;

fn foo() -> FmtResult<()> {
fn foo() -> IoResult<()> {

In other words, as lets us pick a differnet final name for this path. It will still refer to the original definition, but under a different name. Sometimes this can be a good way to avoid conflicts.

Using external packages

If you read Chapter 2, you programmed a guessing game. That project used an external package, rand, to get random numbers. To use rand in your own project, you add this to your Cargo.toml:

[dependencies]
rand = "0.5.5"

And now, you can use use with the name of the crate, rand, to bring stuff into scope:

use rand::Rng;

// Rng can now be used.

It's that easy!

Note that the standard library is a crate, and that means it's external to your crate. You don't need to change Cargo.toml to include std, but you can refer to it in use:


# #![allow(unused_variables)]
#fn main() {
use std::collections::HashMap;
#}

This is an absolute path, starting with the name of the crate: std.

Nested imports for cleaning up large import lists

The guessing game project also had multiple imports with a common prefix, like this:


# #![allow(unused_variables)]
#fn main() {
use std::io;
use std::cmp::Ordering;
#}

We can use 'nested paths' to make this a bit shorter:


# #![allow(unused_variables)]
#fn main() {
use std::{
    io,
    cmp::Ordering,
};
#}

Additionally, if we want to say, de-duplicate this:


# #![allow(unused_variables)]
#fn main() {
use std::io;
use std::io::Write;
#}

We can use self in the nested path:


# #![allow(unused_variables)]
#fn main() {
use std::io::{self, Write};
#}

This brings both std::io and std::io::Write into scope.

Glob imports with *

If you'd like to bring all public items into scope, you can use a glob import:


# #![allow(unused_variables)]
#fn main() {
use std::collections::*;
#}

Be careful with this! This makes it a little harder to tell what names are in scope.

Glob imports are often used when testing; we'll talk about that in Chapter

  1. They're also sometimes used as part of the "prelude pattern", see the standard library documentation for more.

Putting modules in different files

Finally, you don't have to write all of your modules in the same file! Instead of writing this:


# #![allow(unused_variables)]
#fn main() {
mod branch1 {
    fn leaf() {
        // code goes here
    }
}
#}

You can create a new file, src/branch1.rs, with this in it:


# #![allow(unused_variables)]
#fn main() {
fn leaf() {
    // code goes here
}
#}

And then modify your lib.rs or main.rs like this:

mod branch1;

Using a ; instead of a block tells Rust to load the contents of the module from another file. If we wanted to continue with our example, and put a sub-module inside of src/branch1.rs:

mod branch2;

We would need to create a sub-folder, and a file inside of it. They would be named src/branch1/branch2.rs. If branch2 has any mod declarations inside of it, you'd keep going, making sub-folders as appropriate.