Question
I want to organize a Rust module so that it contains multiple structs, with each struct defined in its own file. For example:
Math/
vector.rs
matrix.rs
complex.rs
I want these types to belong to the same overall module and be usable from main.rs like this:
use math::Vector;
fn main() {
// ...
}
However, Rust's module system is confusing to me, and it is not obvious how to split one module across multiple files. It seems like a module must live entirely in one file.
Is splitting a module this way considered idiomatic Rust? If so, how should it be structured?
Short Answer
By the end of this page, you will understand how Rust modules map to files and folders, how to split a module into submodules across multiple files, and how to expose types cleanly with pub use so they can be imported from a single module path.
Concept
Rust modules are primarily about organizing code and controlling visibility, not just about matching folders on disk.
A common beginner misunderstanding is thinking that one module must be written in one file. In Rust, a module can be declared in one place and its contents can be loaded from other files. This is the normal way to build larger projects.
The key ideas are:
mod name;declares a module and tells Rust to load it from another file.pub mod name;does the same thing, but makes that module public.pub use ...;re-exports items so users can access them from a simpler path.- Files and folders help Rust find module code, but the module tree is what really matters.
For your math example, each file like vector.rs, matrix.rs, and complex.rs is usually its own submodule inside a parent math module.
That means the real structure is not “one module split invisibly across files,” but rather:
- a parent module:
math - child modules:
math::vector,math::matrix,math::complex
Then, if you want use math::Vector;, you can re-export Vector from the parent module.
This matters in real programming because codebases quickly grow beyond one file. Rust's module system gives you a way to:
- keep related code together
- hide internal details
- expose a clean public API
- scale from tiny programs to libraries and large applications
Mental Model
Think of a Rust module like a folder with a front desk.
- The folder contains smaller rooms:
vector,matrix,complex - Each room has its own file
- The front desk is the parent
mathmodule - The front desk can either:
- tell visitors to go into a room directly:
math::vector::Vector - hand visitors the item directly:
math::Vectorusingpub use
- tell visitors to go into a room directly:
So the parent module is not forced to contain all the code itself. It can simply coordinate and expose things from its child modules.
Syntax and Examples
The most common way to structure this in Rust is:
src/
main.rs
math/
mod.rs
vector.rs
matrix.rs
complex.rs
main.rs
mod math;
use math::Vector;
fn main() {
let v = Vector { x: 3.0, y: 4.0 };
println!("({}, {})", v.x, v.y);
}
math/mod.rs
pub mod vector;
pub mod matrix;
pub mod complex;
pub use vector::Vector;
pub use matrix::Matrix;
pub use complex::Complex;
math/vector.rs
pub struct Vector {
pub x: f64,
pub y: f64,
}
math/matrix.rs
pub struct Matrix {
pub values: [[f64; 2]; 2],
}
math/complex.rs
pub struct Complex {
pub real: f64,
pub imag: f64,
}
Now both of these paths can work depending on what you expose:
use math::vector::Vector;
or, if you re-export it:
use math::Vector;
What this means
mod math;inmain.rstells Rust to load themathmodule.pub mod vector;inmath/mod.rstells Rust thatvector.rsis a child module.pub use vector::Vector;makesVectoravailable directly asmath::Vector.
Newer file layout style
Modern Rust also supports this layout:
src/
main.rs
math.rs
math/
vector.rs
matrix.rs
complex.rs
Where math.rs contains:
pub mod vector;
pub mod matrix;
pub mod complex;
pub use vector::Vector;
pub use matrix::Matrix;
pub use complex::Complex;
This is often preferred over mod.rs in newer code, though both are valid.
Step by Step Execution
Consider this example:
// main.rs
mod math;
use math::Vector;
fn main() {
let v = Vector { x: 1.0, y: 2.0 };
println!("{} {}", v.x, v.y);
}
// math.rs or math/mod.rs
pub mod vector;
pub use vector::Vector;
// math/vector.rs
pub struct Vector {
pub x: f64,
pub y: f64,
}
Step by step
- Rust starts compiling
main.rs. - It sees
mod math;. - Rust looks for the
mathmodule in either:src/math.rs, orsrc/math/mod.rs
- Inside that file, Rust sees
pub mod vector;. - Rust looks for
src/math/vector.rs. - It loads the
Vectorstruct from that file. pub use vector::Vector;re-exportsVectorat themathlevel.- Because of that re-export,
use math::Vector;works inmain.rs. main()creates aVectorvalue and prints its fields.
Without the re-export
If you removed this line:
pub use vector::Vector;
then this would fail:
use math::Vector;
and you would need:
use math::vector::Vector;
Real World Use Cases
Splitting modules across files is extremely common in real Rust projects.
1. Domain models
A project may group related types together:
user/
profile.rs
settings.rs
permissions.rs
This keeps each type focused and easier to maintain.
2. API clients
An HTTP client crate might organize code by API area:
api/
users.rs
payments.rs
invoices.rs
The parent module can re-export the most important public types.
3. Math or graphics libraries
Types like vectors, matrices, colors, transforms, and quaternions are often split into separate files but grouped under one module tree.
4. Compiler or parser code
Larger tools often have modules such as:
ast/
expr.rs
stmt.rs
types.rs
Each file handles one part of the language model.
5. Internal application organization
Even non-library applications use modules to keep code readable, testable, and easier to navigate.
Real Codebase Usage
In real projects, developers usually combine submodules with carefully chosen public exports.
Common pattern: internal structure, simple public API
pub mod vector;
pub mod matrix;
pub use vector::Vector;
pub use matrix::Matrix;
This gives you:
- internal organization:
math::vector,math::matrix - simple external usage:
math::Vector,math::Matrix
Pattern: hide implementation details
Sometimes the child modules are not made public:
mod vector;
mod matrix;
pub use vector::Vector;
pub use matrix::Matrix;
Now users can access math::Vector, but not math::vector::... directly. This is useful when you want a cleaner API surface.
Pattern: grouped responsibilities
Real codebases often keep:
- one file per major type
- one parent module for shared exports
- private helper modules for internal logic
Pattern: visibility control
Rust developers use visibility deliberately:
pubfor public API- no
pubfor internal implementation pub(crate)for crate-wide access only
This helps prevent accidental misuse and keeps APIs stable.
Pattern: re-exports for ergonomics
Re-exports are common in libraries because they reduce import friction:
pub use error::Result;
pub use client::Client;
This allows users to learn fewer paths while the crate remains well-organized internally.
Common Mistakes
1. Thinking multiple files automatically become one module
Each file is usually a separate module unless declared otherwise.
Broken expectation:
math/
vector.rs
matrix.rs
Rust does not automatically combine those into one math module by name alone. You still need a parent module file.
2. Forgetting to declare child modules
If math.rs does not declare mod vector;, Rust will not load vector.rs.
Broken example:
// math.rs
pub use vector::Vector;
This fails because vector was never declared.
Correct version:
mod vector;
pub use vector::Vector;
3. Forgetting pub
A type may exist but still be inaccessible.
Broken example:
struct Vector {
x: f64,
y: f64,
}
If you want to use it outside its module, it usually needs to be public:
pub struct Vector {
pub x: f64,
pub y: f64,
}
4. Using the wrong file names
Rust module names are conventionally lowercase.
Prefer:
vector.rs
matrix.rs
complex.rs
Not:
Vector.rs
Matrix.rs
Complex.rs
5. Confusing mod with use
moddeclares or defines a moduleusebrings a path into scope
Broken idea:
use math;
This does not declare the module. You still need:
mod math;
6. Re-exporting with the wrong path
Inside a module file, the path must refer to the child module correctly.
Correct:
pub use vector::Vector;
If needed, you can also be explicit:
pub use self::vector::Vector;
Comparisons
| Concept | Purpose | Example | When to use |
|---|---|---|---|
mod | Declare a module | mod math; | When telling Rust where module code lives |
use | Bring a name into scope | use math::Vector; | When you want shorter names in the current file |
pub mod | Declare a public child module | pub mod vector; | When external code should access the child module path |
pub use | Re-export an item | pub use vector::Vector; | When you want a cleaner public API |
math::vector::Vector vs math::Vector
| Path style | Meaning | Benefit | Tradeoff |
|---|---|---|---|
math::vector::Vector | Access through the child module | More explicit structure | Longer import path |
math::Vector | Access via re-export | Cleaner public API | Slightly hides internal layout |
mod.rs vs math.rs
| Style | Example | Status | Notes |
|---|---|---|---|
| Folder entry file | math/mod.rs | Valid and common | Older style, still widely used |
| Flat entry file | math.rs with math/ folder | Valid and common | Often preferred in newer code |
Public child modules vs private child modules
| Pattern | Example | Result |
|---|---|---|
| Public child module | pub mod vector; | Users can access math::vector::Vector |
| Private child module + re-export | mod vector; pub use vector::Vector; | Users can access math::Vector, but not the module internals directly |
Cheat Sheet
// main.rs
mod math;
use math::Vector;
// math.rs or math/mod.rs
mod vector;
pub use vector::Vector;
// math/vector.rs
pub struct Vector {
pub x: f64,
pub y: f64,
}
Rules to remember
mod name;tells Rust to load a module from a file.- A parent module must declare its child modules.
usedoes not create modules; it only imports names.pubcontrols visibility.pub usere-exports names from one module level to another.- Files are usually lowercase:
vector.rs, notVector.rs.
Common layouts
src/
main.rs
math.rs
math/
vector.rs
or
src/
main.rs
math/
mod.rs
vector.rs
Import options
use math::vector::Vector;
use math::Vector;
The second form requires a re-export.
Visibility shortcuts
pub= visible outside the modulepub(crate)= visible inside the crate- no
pub= private to the module
FAQ
Can a Rust module be split across multiple files?
Yes. This is a normal and idiomatic way to organize Rust code. Usually, a parent module declares child modules stored in separate files.
Why doesn't Rust automatically combine files in a folder into one module?
Rust requires explicit module declarations so the module tree is clear and visibility is controlled intentionally.
Should I use mod.rs or math.rs?
Both are valid. Many newer codebases prefer math.rs plus a math/ folder, but mod.rs is still completely acceptable.
How do I make use math::Vector; work?
Re-export the type from the parent module:
pub use vector::Vector;
What is the difference between mod and use in Rust?
mod declares a module. use imports a name from an existing module path into the current scope.
Do I need pub mod vector; or just mod vector;?
Use pub mod vector; if external code should access math::vector. Use mod vector; if you only want to expose selected items via pub use.
Can I keep the child modules private and still expose their structs?
Yes. This is a common pattern:
mod vector;
pub use vector::Vector;
Are uppercase file names like Vector.rs idiomatic in Rust?
No. Rust convention is lowercase module file names such as vector.rs.
Mini Project
Description
Build a small Rust math module that stores several numeric types in separate files while exposing a clean API from one parent module. This demonstrates how Rust modules, child modules, and re-exports work together in a practical project structure.
Goal
Create a math module with separate files for Vector, Matrix, and Complex, then use those types from main.rs through a clean import path.
Requirements
- Create a parent
mathmodule that loads child modules from separate files. - Define one public struct in each child module:
Vector,Matrix, andComplex. - Re-export the structs so they can be imported directly from
math. - In
main.rs, create at least one value of each type and print something from it.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.