Sage-Code Laboratory
index<--

Rust Modules

As programs get larger, it's necessary to spread them over more than one file and put functions and types in different "namespaces". The Rust solution for both of these is "modules". We call this component oriented architecture.

Page bookmarks



What is a module?

Unlike other programming languages, a module do not represent a file. It represents something else. It is very similar to a "namespace". One source file *.rs can define many modules.

Each module can contain other modules inside. This will create a hierarchy. You can use two columns "::" as delimiter to identify a module component similar to URL that is using "/" as delimiter.

Example: module::sub-module::component-name

Public Module

You can use "pub" keyword to define public modules. This indicate that a module member can be used outside of module scope. You can use "*" to import all "pub" members defined into a module or you can specify each member to be imported by name. After import you can use the members as part of current scope.

Example:

In this example modules are defined in a single file:

pub mod first_module {
  pub mod sub_module {
     pub fn test() {
       println!("You found me again");
     }
  } //end sub_module

  pub fn test() {
     println!("You found me");   
  }
} // end first_module

// import in current namespace all elements of first_module
use first_module::*;

fn main() {
  test(); // You found me
  sub_module::test(); // You found me again
}

Notes:

Homework: This example can be tested live: modules

What is a crate?

In Rust, a crate is a binary or library that can be compiled and executed. A crate can contain one or more modules, and each module can be nested within other modules.

A crate begins with a root module, which is defined in a file with the same name as the crate. All other modules in the crate can then be organized hierarchically under the root module.

For example, suppose we have a crate named "my_crate". We can define the root module in a file named "main.rs" or "lib.rs", and then define other modules that belong to this crate like this:


// main.rs or lib.rs

mod my_module {
    // module code
}

mod another_module {
    // module code
}

In this example, our crate has two modules: "my_module" and "another_module", both of which are defined within the root module of the crate. Each module can then contain functions, structs, enums, and other modules.

We can also define modules that belong to a specific file in the crate. This is useful for organizing code that is related to a specific functionality:

// my_module.rs

pub mod sub_module {
    // module code
}

In this example, we define a module named "sub_module" that belongs to the "my_module" module. The "pub" keyword makes this module accessible to code outside of the "my_module" module.

In summary, crates are the top-level organizational unit in Rust programs, while modules are used to organize code within a crate. Each crate can contain one or more modules, which can be nested within other modules to organize the code hierarchically.

Usability

In Rust, modules are an important feature that allows you to organize your code into logical groups. Each module can hold code related to a specific functionality, making it easier to navigate and understand a codebase. Here are some important things to know about modules in Rust:

  1. Modularization: Modules provide a way to modularize your code, which makes it easier to maintain and understand. You can group related code together in modules and hide the inner workings of each module from the outside world by using the pub and mod keywords.
  2. Privacy: Rust uses the concept of privacy to control access to functions, types, and other constructs defined in a module. By default, everything in a module is private, meaning it can only be accessed from within the same module. If you want to make something public so that it can be accessed from outside the module, you need to use the pub keyword.
  3. Namespace: Modules create a namespace for your code, allowing you to avoid naming conflicts. You can define multiple functions, types, and other constructs with the same name in different modules without causing a name collision.
  4. Filesystem hierarchy: Rust has a convention for how to organize modules on the filesystem. Each file in a crate corresponds to a module, and the directory structure represents the nested module hierarchy. For example, a file named foo.rs can define a module named foo that is a child of the root module.

Modules are an important feature in Rust, but they're not strictly required. You can write all your code in a single file if you want to, but as your code grows in complexity, it will become harder to maintain and understand without proper organization. Modules provide a way to divide your code into smaller, more manageable pieces, making it easier to reason about and maintain over time.

Versions

In Rust, modules don't have versions on their own. Versioning is mainly used to manage dependencies between different modules or crates, which are collections of modules.

Each crate has a version listed in its "Cargo.toml" manifest file. When you specify a dependency on a crate in your own "Cargo.toml", you can require a specific version or version range of that crate.

For example, suppose your crate depends on "serde", a popular Rust library for serializing and deserializing data. You could specify a specific version of "serde", like this:

[dependencies]
serde = "1.0.104"

Or, you could specify a version range, like this:

[dependencies]
serde = "^1.0.104"

The caret ("^") character means that your crate can use any version of "serde" that is compatible with version 1.0.104, but not any major version that might break compatibility.

To update a crate's version, you can manually change the version in "Cargo.toml" and then run "cargo update". Alternatively, you can automatically update all dependencies to their latest compatible versions by running "cargo update --aggressive".

Rust's package manager, Cargo, automatically resolves and downloads the versions of crates specified in your "Cargo.toml" file, as well as any dependencies those crates may have. This helps ensure that your project uses compatible and up-to-date versions of all the dependencies it needs.

What is Cargo?

In Rust language, cargo is the default package manager and build tool. It is used for managing dependencies and building, testing, and packaging Rust code.

The cargo tool creates, builds and manages Rust projects. It also helps you automate tasks frequently required when building Rust software such as compiling source code, downloading and compiling various dependent libraries, running tests, and creating distributable packages.

To use Cargo, you need to have Rust installed on your system. Once you have Rust installed, you can use the cargo command to create, build and manage your Rust projects.

Here are some common commands that you can use with Cargo:

Cargo also helps you manage dependencies by providing a simple and easy way of using external crates in your Rust project. You can specify your project's dependencies in the Cargo.toml file that is created when you create a new Rust project using Cargo.

Here's an example Cargo.toml file:


[package]
name = "my-app"
version = "0.1.0"
authors = ["Your Name <your-email@example.com>"]
edition = "2018"

[dependencies]
rand = "0.8.0"

In this example, we've specified a dependency on the rand crate, version 0.8.0.

That's a brief overview of what cargo is in Rust and how to use it. I hope that helps!

What is .toml

A ".toml" file is a configuration file format popularly used in Rust for specifying package metadata and configuration information. The name TOML stands for "Tom's Obvious, Minimal Language," created by Tom Preston-Werner in 2013. The syntax of TOML files is intended to be easy to read and parse by both humans and machines.

example

toml
[package]
name = "my_cool_project"
version = "0.1.0"
authors = ["Your Name yourname@example.com"]

[dependencies]
rand = "0.7.3"

In this example, we have two sections: "[package]" and "[dependencies]". The "package" section contains metadata about the package, such as its name, version, and author. The "dependencies" section lists the dependencies of the package, along with their versions.

The syntax of ".toml" files is based on key/value pairs. Sections are defined by enclosing their names in brackets ("[" and "]"). Within each section, the values are specified as "key = "value"" pairs. Values can be strings, integers, booleans, or arrays of any of these types. Comments in TOML files are started with the "#" character.

Rust uses ".toml" files for package metadata and configuration because they are easy to read and write, and they can be easily parsed by Rust's built-in "toml" crate. The "toml" crate allows Rust programs to read and write TOML files, making them a useful tool for Rust developers.


Read next: Errors