RustModules

Syntax

  • mod modname; // Search for the module in modname.rs or modname/mod.rs in the same directory
  • mod modname { block }

Modules tree

Files:

- example.rs (root of our modules tree, generally named lib.rs or main.rs when using Cargo)
- first.rs
- second/
  - mod.rs
  - sub.rs

Modules:

- example        -> example
  - first        -> example::first
  - second       -> example::second
    - sub        -> example::second::sub
  - third        -> example::third

example.rs

pub mod first;
pub mod second;
pub mod third {
    ...
}

The module second have to be declared in the example.rs file as its parent is example and not, for example, first and thus cannot be declared in the first.rs or another file in the same directory level

second/mod.rs

pub mod sub;

The #[path] attribute

Rust's #[path] attribute can be used to specify the path to search for a particular module if it is not in the standard location. This is typically discouraged, however, because it makes the module hierarchy fragile and makes it easy to break the build by moving a file in a completely different directory.

#[path="../path/to/module.rs"]
mod module;

Names in code vs names in `use`

The double-colon syntax of names in the use statement looks similar to names used elsewhere in the code, but meaning of these paths is different.

Names in the use statement by default are interpreted as absolute, starting at the crate root. Names elsewhere in the code are relative to the current module.

The statement:

use std::fs::File;

has the same meaning in the main file of the crate as well as in modules. On the other hand, a function name such as std::fs::File::open() will refer to Rust's standard library only in the main file of the crate, because names in the code are interpreted relative to the current module.

fn main() {
    std::fs::File::open("example"); // OK
}

mod my_module {
   fn my_fn() {
       // Error! It means my_module::std::fs::File::open()
       std::fs::File::open("example"); 

       // OK. `::` prefix makes it absolute 
       ::std::fs::File::open("example"); 

       // OK. `super::` reaches out to the parent module, where `std` is present
       super::std::fs::File::open("example"); 
   } 
}

To make std::… names behave everywhere the same as in the root of the crate you could add:

use std;

Conversely, you can make use paths relative by prefixing them with self or super keywords:

 use self::my_module::my_fn;

Accessing the Parent Module

Sometimes, it can be useful to import functions and structs relatively without having to use something with its absolute path in your project. To achieve this, you can use the module super, like so:

fn x() -> u8 {
    5
}

mod example {
    use super::x;

    fn foo() {
        println!("{}", x());
    }
}

You can use super multiple times to reach the 'grandparent' of your current module, but you should be wary of introducing readability issues if you use super too many times in one import.

Exports and Visibility

Directory structure:

yourproject/
    Cargo.lock
    Cargo.toml
    src/
        main.rs
        writer.rs

main.rs

// This is import from writer.rs
mod writer;

fn main() {
    // Call of imported write() function.
    writer::write()
    
    // BAD
    writer::open_file()
}

writer.rs

// This function WILL be exported.
pub fn write() {}

// This will NOT be exported.
fn open_file() {}

Basic Code Organization

Let's see how we can organize the code, when the codebase is getting larger.

01. Functions

fn main() {
  greet();
}

fn greet() {
  println!("Hello, world!");
}

02. Modules - In the same file

fn main() {
  greet::hello();
}

mod greet {
  // By default, everything inside a module is private
  pub fn hello() { // So function has to be public to access from outside
    println!("Hello, world!");
  }
}

03. Modules - In a different file in the same directory

When move some code to a new file, no need to wrap the code in a mod declaration. File itself acts as a module.

// ↳ main.rs
mod greet; // import greet module

fn main() {
  greet::hello();
}
// ↳ greet.rs
pub fn hello() { // function has to be public to access from outside
  println!("Hello, world!");
}

When move some code to a new file, if that code has been wrapped from a mod declaration, that will be a sub module of the file.

// ↳ main.rs
mod greet;

fn main() {
  greet::hello::greet();
}
// ↳ greet.rs
pub mod hello { // module has to be public to access from outside
  pub fn greet() { // function has to be public to access from outside
    println!("Hello, world!");
  }
}

04. Modules - In a different file in a different directory

When move some code to a new file in a different directory, directory itself acts as a module. And mod.rs in the module root is the entry point to the directory module. All other files in that directory, acts as a sub module of that directory.

// ↳ main.rs
mod greet;

fn main() {
  greet::hello();
}
// ↳ greet/mod.rs
pub fn hello() {
  println!("Hello, world!");
}

When you have multiple files in the module root,

// ↳ main.rs
mod greet;

fn main() {
  greet::hello_greet()
}
// ↳ greet/mod.rs
mod hello;

pub fn hello_greet() {
  hello::greet()
}
// ↳ greet/hello.rs
pub fn greet() {
  println!("Hello, world!");
}

05. Modules - With self

fn main() {
  greet::call_hello();
}

mod greet {
  pub fn call_hello() { 
    self::hello();
  }

  fn hello() {
    println!("Hello, world!");
  }
}

06. Modules - With super

  1. When you want to access a root function from inside a module,
fn main() {
  dash::call_hello();
}

fn hello() {
  println!("Hello, world!");
}

mod dash {
  pub fn call_hello() {
    super::hello();
  }
}
  1. When you want to access a function in outer/ parent module from inside a nested module,
fn main() {
  outer::inner::call_hello();
}

mod outer {

  pub fn hello() {
    println!("Hello, world!");
  }

  mod inner {
    pub fn call_hello() {
      super::hello();
    }
  }

}

07. Modules - With use

  1. When you want to bind the full path to a new name,
use greet::hello::greet as greet_hello;

fn main() {
  greet_hello();
}

mod greet {
  pub mod hello {
    pub fn greet() {
      println!("Hello, world!");
    }
  }
}
  1. When you want to use crate scope level content
fn main() {
  user::hello();
}

mod greet {
  pub mod hello {
    pub fn greet() {
      println!("Hello, world!");
    }
  }
}

mod user {
  use greet::hello::greet as call_hello;

  pub fn hello() {
    call_hello();
  }
}