Modules, Packages & Cargo

Rust module system, visibility, use statements, crate structure, and Cargo workflow

Rust’s module system controls privacy and organization. The default is private — you must explicitly mark items as pub to expose them. Cargo is the build system and package manager.

Module basics

// mod declares a module (can be inline or in a separate file)
mod network {
    // Private by default
    fn connect() -> bool { true }

    // pub makes it accessible outside this module
    pub fn send(data: &str) -> bool {
        if connect() {
            println!("Sending: {}", data);
            true
        } else {
            false
        }
    }

    // pub(crate) — visible within the crate, not externally
    pub(crate) fn internal_log(msg: &str) {
        println!("[internal] {}", msg);
    }

    // Nested module
    pub mod protocol {
        pub fn parse(header: &str) -> String {
            format!("Parsed: {}", header)
        }
    }
}

// Access module items
network::send("hello");
network::protocol::parse("HTTP");
// network::connect(); // ERROR: private

use — bringing items into scope

// Single item
use std::collections::HashMap;

// Multiple items
use std::fs::{self, File};  // fs module + File type

// Glob (use sparingly)
use std::io::prelude::*;

// Aliasing with as
use std::io::Result as IoResult;

// Re-exporting with pub use
pub use crate::network::protocol::parse;

File-based modules

Rust maps modules to the filesystem:

src/
├── main.rs          // crate root for binary
├── lib.rs           // crate root for library
└── network/
    ├── mod.rs        // declares `mod network`
    ├── server.rs     // network::server
    └── protocol.rs   // network::protocol
// In src/lib.rs (crate root)
pub mod network;    // tells Rust to look in src/network/mod.rs

// In src/network/mod.rs
pub mod server;     // src/network/server.rs
pub mod protocol;   // src/network/protocol.rs

// In src/network/server.rs
pub fn start() { /* ... */ }

Modern Rust (2018+): You can use network.rs instead of network/mod.rs for the top-level module file. The mod.rs convention is still common but both work.

Visibility cheat sheet

fn private() {}           // private — module only
pub fn public() {}        // public — anyone
pub(crate) fn crate_wide() {}    // crate only
pub(super) fn parent_only() {}    // parent module only
pub(in path) fn specific() {}    // specific path only
Visibility Accessible from
(no pub) Same module and descendants
pub(super) Parent module
pub(crate) Entire crate
pub Everyone (including external crates)
pub(in path) Specified path

Cargo — the build system

Project structure

myproject/
├── Cargo.toml          // project metadata & dependencies
├── Cargo.lock          // locked dependency versions (git this)
├── src/
│   ├── main.rs         // binary entry point
│   └── lib.rs          // library (optional)
├── tests/              // integration tests
│   └── integration_test.rs
├── benches/            // benchmarks
└── examples/           // examples
    └── demo.rs

Cargo.toml

[package]
name = "myproject"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1"

[dev-dependencies]
assert_cmd = "2"
proptest = "1"

[profile.release]
opt-level = 3
lto = true

Common commands

cargo new myproject       # create new project
cargo new --lib mylib     # create library project
cargo init                # init in existing directory
cargo build               # compile (debug mode)
cargo build --release     # compile with optimizations
cargo check               # type-check only (much faster)
cargo run                 # build and run
cargo run -- arg1 arg2    # run with arguments
cargo test                # run all tests
cargo test test_name      # run specific test
cargo test -- --nocapture # show println! output
cargo fmt                 # format code
cargo clippy              # lint code
cargo doc --open          # generate and open docs
cargo add serde           # add dependency
cargo add tokio --features full  # add with features
cargo update              # update dependencies
cargo tree                # show dependency tree

Workspace — multi-crate projects

# In workspace root Cargo.toml
[workspace]
members = [
    "app",
    "core",
    "api",
]
workspace/
├── Cargo.toml      # workspace root
├── app/
│   └── Cargo.toml  # binary crate
├── core/
│   └── Cargo.toml  # library crate
└── api/
    └── Cargo.toml  # library crate

Testing

// Unit tests (in same file, marked with #[cfg(test)])
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }

    #[test]
    fn test_error() {
        let result = divide(10, 0);
        assert!(result.is_err());
    }

    #[test]
    #[should_panic(expected = "division by zero")]
    fn test_panic() {
        divide(10, 0);
    }

    #[test]
    fn test_option() -> Option<()> {
        let val = parse("42")?;
        assert_eq!(val, 42);
        Some(())
    }
}

Integration tests go in tests/ directory — each file is a separate crate:

// tests/integration_test.rs
use myproject::add;

#[test]
fn test_integration() {
    assert_eq!(add(2, 3), 5);
}

Feature flags

# Cargo.toml
[features]
default = ["json"]
json = ["serde_json"]
full = ["json", "tls"]

[dependencies]
serde_json = { version = "1.0", optional = true }
// Conditional compilation
#[cfg(feature = "json")]
pub fn parse_json(input: &str) -> Value {
    serde_json::from_str(input).unwrap()
}

Common Cargo.toml patterns

# Specific version
serde = "1.0"

# With features
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

# Git dependency
mylib = { git = "https://github.com/user/mylib", branch = "main" }

# Local path dependency
common = { path = "../common" }

# Development-only dependency
[dev-dependencies]
criterion = "0.5"