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…