Inline Modules
An inline module is a module whose entire body is written directly inside curly braces (mod name { ... }) within the current source file, rather than loaded from a separate .rs file on disk.
Syntax and Structure of an Inline Module
An inline module can be placed at the top level of a crate root (src/main.rs or src/lib.rs), nested inside another module, or even declared locally inside a function body:
mod billing {
pub const CURRENCY: &str = "USD";
#[derive(Debug)]
pub struct Invoice {
pub id: u64,
amount_cents: u64, // Private field: accessible only inside `mod billing`
}
impl Invoice {
pub fn new(id: u64, amount_cents: u64) -> Self {
Self { id, amount_cents }
}
pub fn formatted_total(&self) -> String {
format!("{}.{0:02} {}", self.amount_cents / 100, self.amount_cents % 100, CURRENCY)
}
}
}
fn main() {
let inv = billing::Invoice::new(1001, 4999);
println!("Invoice #{}: {}", inv.id, inv.formatted_total());
}
Invoice #1001: 49.99 USD
Isolated Namespace of an Inline Module
Even though an inline module is written inside the same physical .rs file as the surrounding code, it does not automatically inherit use imports or sibling items from the parent scope!
Inside mod child { ... }, the namespace starts completely clean (containing only the standard library prelude). To refer to items or imports from the enclosing file inside an inline module, prefix them with super:: (or use super::*;):
+-------------------------------------------------------------------+
| // Enclosing File (`crate`) |
| use std::collections::HashMap; |
| struct SharedItem; |
| |
| mod inner { |
| // `HashMap` and `SharedItem` are NOT in scope by default! |
| // Bring them in explicitly with `use super::*;` |
| use super::*; |
| } |
+-------------------------------------------------------------------+
use std::collections::HashMap;
struct AppVersion(u32, u32);
mod diagnostics {
// Import everything from the parent scope (`HashMap` and `AppVersion`):
use super::*;
pub fn build_report() -> HashMap<&'static str, String> {
let ver = AppVersion(1, 85);
let mut map = HashMap::new();
map.insert("version", format!("{}.{}", ver.0, ver.1));
map
}
}
fn main() {
println!("Report: {:?}", diagnostics::build_report());
}
Report: {"version": "1.85"}
Primary Use Cases for Inline Modules
1. Unit Test Modules (#[cfg(test)] mod tests)
By far the most common inline module in every Rust codebase is the unit test module placed at the bottom of a source file.
Because child modules have access to private functions and fields of their parent module (use super::*;), placing #[cfg(test)] mod tests { ... } inline at the bottom of a file lets you test internal private helper functions directly while compiling the tests module only when cargo test runs:
// Private helper function (not `pub`):
fn normalize_tag(raw: &str) -> String {
raw.trim().to_ascii_lowercase().replace(' ', "_")
}
#[cfg(test)]
mod tests {
use super::*; // Brings private `normalize_tag` into scope!
#[test]
fn normalizes_spaces_and_case() {
assert_eq!(normalize_tag(" Rust Edition 2024 "), "rust_edition_2024");
}
}
fn main() {
println!("Normalized: {}", normalize_tag(" Hello World "));
}
Normalized: hello_world
2. Enforcing Privacy Boundaries Inside a Single File
Remember that Rust's privacy boundary is the module, not the struct or the file! Any code in the same module as a struct can read and write its private fields directly.
If you want to guarantee that even the rest of the current file cannot accidentally bypass a struct's constructor or mutate its private fields, wrap that struct and its impl block in a small inline module:
mod bounded {
#[derive(Debug, Clone, Copy)]
pub struct NonEmptySlice<'a, T>(&'a [T]); // Private `.0` field!
impl<'a, T> NonEmptySlice<'a, T> {
pub fn new(slice: &'a [T]) -> Option<Self> {
if slice.is_empty() {
None
} else {
Some(Self(slice))
}
}
pub fn first(&self) -> &'a T {
// Safe: invariant guarantees `.0` has at least 1 element!
&self.0[0]
}
}
}
fn main() {
// Even though `main` is in the same file, it is outside `mod bounded`
// and CANNOT construct `NonEmptySlice(&[])` directly!
if let Some(non_empty) = bounded::NonEmptySlice::new(&[10, 20, 30]) {
println!("First element: {}", non_empty.first());
}
}
First element: 10
3. Grouping Generated Code, FFI Bindings, or Scoped Lint Overrides
Inline modules are also ideal when you want to apply an attribute—such as #[allow(non_camel_case_types)] for C FFI bindings or #[allow(dead_code)] for experimental prototypes—to a group of items at once without applying it to the entire file.