iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Modules & Crates

Rust’s module system organises code into a tree: mod declares submodules, pub controls visibility, use imports paths. Combined with Cargo.toml for crates and workspaces, you scale from a single binary to multi-crate monorepos.

mod, pub, use, crates, workspaces

EXAMPLE
// 1) Single-file module
// src/main.rs
fn main() {
    println!("{}", math::add(2, 3));
}

mod math {
    pub fn add(a: i32, b: i32) -> i32 { a + b }
    pub fn mul(a: i32, b: i32) -> i32 { a * b }
}

// 2) Multi-file modules — file or directory
// src/main.rs
mod math;          // resolves to src/math.rs OR src/math/mod.rs

fn main() {
    println!("{}", math::add(2, 3));
}

// src/math.rs
pub fn add(a: i32, b: i32) -> i32 { a + b }
pub fn mul(a: i32, b: i32) -> i32 { a * b }

// 3) Nested modules — directory form
// src/math/mod.rs (older) OR src/math.rs + src/math/ folder
// src/math.rs
pub mod arithmetic;
pub mod geometry;

// src/math/arithmetic.rs
pub fn add(a: i32, b: i32) -> i32 { a + b }

// src/math/geometry.rs
pub fn area(w: f64, h: f64) -> f64 { w * h }

// Usage in main.rs:
fn main() {
    println!("{}", math::arithmetic::add(2, 3));
}

// 4) Visibility
// pub                      — public anywhere
// pub(crate)               — visible within the crate
// pub(super)                — visible to the parent module
// pub(in path)              — visible to a specific module
// (default — private)       — only same module

mod database {
    pub fn connect() {}
    pub(crate) fn migrate() {}      // crate-internal API
    fn parse_dsn() {}                // private helper
}

// 5) use — import paths
use std::collections::HashMap;
use std::io::{self, Read, Write};                   // import multiple
use std::fmt::{Display, Debug};
use crate::math::arithmetic::add;
use crate::math::arithmetic::add as math_add;       // alias

use super::sibling_module;
use self::child_module;
use crate::other_module;

// 6) Re-export — pub use
// src/lib.rs
mod internal;
pub use internal::PublicType;                        // expose internal::PublicType as crate::PublicType

// Consumers do:
// use my_crate::PublicType;

// 7) The 2018+ idioms
// • Drop 'extern crate' — use Cargo.toml + 'use'
// • Drop mod.rs — prefer src/foo.rs + src/foo/sub.rs
// • Drop 'self::' before paths — implicit

// 8) Cargo workspaces — multi-crate monorepo
// Cargo.toml at root
[workspace]
members = [
    "crates/api",
    "crates/core",
    "crates/cli",
]
resolver = "2"

[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }

// crates/api/Cargo.toml
[package]
name = "api"
version = "0.1.0"
edition = "2021"

[dependencies]
core = { path = "../core" }
serde = { workspace = true }
tokio = { workspace = true }

// 9) Library vs binary
// Cargo.toml
[lib]
name = "mycrate"
path = "src/lib.rs"

[[bin]]
name = "server"
path = "src/bin/server.rs"

[[bin]]
name = "cli"
path = "src/bin/cli.rs"

// src/bin/server.rs uses mycrate via:
use mycrate::*;

// 10) Tests + integration tests
// Unit tests inside the module
mod math {
    pub fn add(a: i32, b: i32) -> i32 { a + b }

    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn it_adds() { assert_eq!(add(2, 3), 5); }
    }
}

// Integration tests in tests/
// tests/integration.rs
use mycrate::math::add;

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

// 11) Conditional compilation
#[cfg(target_os = "linux")]
fn platform_specific() { /* linux only */ }

#[cfg(feature = "async")]
pub mod async_api;

// Cargo.toml
[features]
default = []
async   = ["tokio", "futures"]

// 12) Re-export patterns for clean public API
// src/lib.rs
mod error;
mod client;
mod transport;

pub use error::Error;
pub use client::Client;
pub use transport::Transport;

// Internals stay private; only what's exported via pub use is the public API.

// 13) prelude pattern — common imports for users
// src/lib.rs
pub mod prelude {
    pub use crate::Client;
    pub use crate::Error;
    pub use crate::Transport;
}

// Consumers:
use mycrate::prelude::*;

// 14) Crate-level docs + attributes
//! Crate-level docs go in src/lib.rs at the top.
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
#![deny(unsafe_code)]

// 15) Common bugs
// • Forgetting 'pub' on types you intend to export → 'private type used'
// • Cyclic module dependencies → compile error; refactor shared types into a separate mod
// • Re-exporting too aggressively — every internal change becomes a breaking change
// • Using full paths everywhere — verbose; use 'use' at top of file
// • use std::* — pulls everything; explicit imports are clearer
// • Missing 'pub' on a function but expecting it visible — at minimum pub(crate)
// • Wrong file structure (src/math/mod.rs + src/math.rs both exist) — Cargo picks one; warning
// • Workspace member paths wrong → 'no such file or directory'
// • Adding feature flags but not gating with #[cfg(feature = ...)] → code always compiled
// • Crate names with dashes ('my-crate') — Rust converts to underscores when 'use my_crate::'

Why it matters

Rust modules grow with the codebase: mod declares structure, pub/pub(crate) control visibility, use imports, and pub use shapes your public API. Single-file modules stay in src/foo.rs; nested ones add src/foo/. Move to Cargo workspaces when one crate becomes too big or you want to publish separate crates.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// src/lib.rs
pub mod math {
    pub fn add(a: i32, b: i32) -> i32 { a + b }
}
Try it Yourself »

Discussion

Loading…