Rust - Modules
Rust - Modules, Crates, and Workspaces
Section titled “Rust - Modules, Crates, and Workspaces”As projects grow, organizing code becomes critical. Rust provides a powerful system for this based on crates, modules, and workspaces.
| Term | Description |
|---|---|
| Crate | The 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). |
| Package | A 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. |
| Module | A way to organize code within a crate into logical units and control the visibility (public/private) of items. |
| Workspace | A set of packages that share a common Cargo.lock and output directory. Ideal for managing large projects with multiple interconnected components. |
Defining and Using Modules
Section titled “Defining and Using Modules”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();}Bringing Paths into Scope with use
Section titled “Bringing Paths into Scope with use”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 scopeuse network::server::listen;
fn main() { // Now we can call it directly listen();}Best Practice
Section titled “Best Practice”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.
Step 1: Create the Workspace
Section titled “Step 1: Create the Workspace”mkdir media_appcd media_apptouch Cargo.tomlEdit the new Cargo.toml at the root of media_app/ to define it as a workspace:
[workspace]
members = [ "media_lib", "media_cli",]Step 2: Create the Library Crate
Section titled “Step 2: Create the Library Crate”Inside the media_app directory, use Cargo to create the library package:
cargo new media_lib --libEdit 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,}Step 3: Create the Binary Crate
Section titled “Step 3: Create the Binary Crate”Now, create the command-line application that will use our library:
cargo new media_cliStep 4: Add the Library as a Dependency
Section titled “Step 4: Add the Library as a Dependency”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" }Step 5: Use the Library in the Binary
Section titled “Step 5: Use the Library in the Binary”Finally, edit media_app/media_cli/src/main.rs to call our library’s code:
// Use items from our local library crateuse 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);}Step 6: Run the Application
Section titled “Step 6: Run the Application”From the root of the workspace (media_app/), you can run the binary crate specifically:
cargo run -p media_cliOutput
Section titled “Output” 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.mp4Now loading audio: 'Time' by Hans Zimmer