Skip to content

Rust - Modules

As projects grow, organizing code becomes critical. Rust provides a powerful system for this based on crates, modules, and workspaces.

TermDescription
CrateThe smallest unit of compilation in Rust. A crate can be a binary (an executable program with a main function) or a library (a collection of reusable code).
PackageA bundle of one or more crates that provides a set of functionality. A package contains a Cargo.toml file describing how to build those crates. A package can contain at most one library crate, but many binary crates.
ModuleA way to organize code within a crate into logical units and control the visibility (public/private) of items.
WorkspaceA set of packages that share a common Cargo.lock and output directory. Ideal for managing large projects with multiple interconnected components.

Modules are declared with the mod keyword. By default, everything inside a module is private. Use the pub keyword to make items (modules, functions, structs, etc.) public and accessible from outside the module.

mod network {
// This function is private to the `network` module.
fn connect() {
println!("Private connect function called");
}
// This module is public.
pub mod server {
// This function is public, so it can be called from outside.
pub fn listen() {
println!("Listening on server...");
// We can call a private function from the same parent module.
super::connect();
}
}
}
fn main() {
// This works because `server` and `listen` are both public.
network::server::listen();
// This would fail to compile because `connect` is private:
// network::connect();
}

Typing out full paths like network::server::listen can be tedious. The use keyword creates a shortcut to a path, making the code more concise.

// ... (network module from above)
// Bring the `listen` function into the current scope
use network::server::listen;
fn main() {
// Now we can call it directly
listen();
}

It’s idiomatic to bring structs, enums, and other items into scope fully with use. For functions, it’s often clearer to bring the parent module into scope and call the function with its module prefix (e.g., server::listen()) to make it obvious where the function comes from.

Project: Creating and Using a Library Crate in a Workspace

Section titled “Project: Creating and Using a Library Crate in a Workspace”

Let’s create a more realistic project structure: a workspace containing a library for our application’s logic and a binary that uses it.

mkdir media_app
cd media_app
touch Cargo.toml

Edit the new Cargo.toml at the root of media_app/ to define it as a workspace:

[workspace]
members = [
"media_lib",
"media_cli",
]

Inside the media_app directory, use Cargo to create the library package:

cargo new media_lib --lib

Edit media_app/media_lib/src/lib.rs to contain our public API:

/// A public function to play media.
pub fn play(name: &str) {
println!("Playing media: {}", name);
}
/// A public struct representing an Audio track.
pub struct Audio {
pub title: String,
pub artist: String,
}

Now, create the command-line application that will use our library:

cargo new media_cli

Edit media_app/media_cli/Cargo.toml to tell it about media_lib:

[package]
name = "media_cli"
version = "0.1.0"
edition = "2021"
[dependencies]
media_lib = { path = "../media_lib" }

Finally, edit media_app/media_cli/src/main.rs to call our library’s code:

// Use items from our local library crate
use media_lib::{play, Audio};
fn main() {
println!("--- Media CLI Application ---");
// Call the library function
play("Interstellar Soundtrack.mp4");
// Use the library struct
let track = Audio {
title: "Time".to_string(),
artist: "Hans Zimmer".to_string(),
};
println!("Now loading audio: '{}' by {}", track.title, track.artist);
}

From the root of the workspace (media_app/), you can run the binary crate specifically:

cargo run -p media_cli
Compiling media_lib v0.1.0 (...)
Compiling media_cli v0.1.0 (...)
Finished dev [unoptimized + debuginfo] target(s) in ...
Running `target/debug/media_cli`
--- Media CLI Application ---
Playing media: Interstellar Soundtrack.mp4
Now loading audio: 'Time' by Hans Zimmer