Question

How to Split a Rust Module Across Multiple Files

rustmodule

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 math module
  • The front desk can either:
    • tell visitors to go into a room directly: math::vector::Vector
    • hand visitors the item directly: math::Vector using pub use

So the parent module is not forced to contain all the code itself. It can simply coordinate and expose things from its child modules.

Take Quiz

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; in main.rs tells Rust to load the math module.
  • pub mod vector; in math/mod.rs tells Rust that vector.rs is a child module.
  • pub use vector::Vector; makes Vector available directly as math::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

  1. Rust starts compiling main.rs.
  2. It sees mod math;.
  3. Rust looks for the math module in either:
    • src/math.rs, or
    • src/math/mod.rs
  4. Inside that file, Rust sees pub mod vector;.
  5. Rust looks for src/math/vector.rs.
  6. It loads the Vector struct from that file.
  7. pub use vector::Vector; re-exports Vector at the math level.
  8. Because of that re-export, use math::Vector; works in main.rs.
  9. main() creates a Vector value 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.

Take Quiz

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:

  • pub for public API
  • no pub for 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

  • mod declares or defines a module
  • use brings 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

ConceptPurposeExampleWhen to use
modDeclare a modulemod math;When telling Rust where module code lives
useBring a name into scopeuse math::Vector;When you want shorter names in the current file
pub modDeclare a public child modulepub mod vector;When external code should access the child module path
pub useRe-export an itempub use vector::Vector;When you want a cleaner public API

math::vector::Vector vs math::Vector

Path styleMeaningBenefitTradeoff
math::vector::VectorAccess through the child moduleMore explicit structureLonger import path
math::VectorAccess via re-exportCleaner public APISlightly hides internal layout

mod.rs vs math.rs

StyleExampleStatusNotes
Folder entry filemath/mod.rsValid and commonOlder style, still widely used
Flat entry filemath.rs with math/ folderValid and commonOften preferred in newer code

Public child modules vs private child modules

PatternExampleResult
Public child modulepub mod vector;Users can access math::vector::Vector
Private child module + re-exportmod vector; pub use vector::Vector;Users can access math::Vector, but not the module internals directly
Take Quiz

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.
  • use does not create modules; it only imports names.
  • pub controls visibility.
  • pub use re-exports names from one module level to another.
  • Files are usually lowercase: vector.rs, not Vector.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 module
  • pub(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.

Take Quiz

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 math module that loads child modules from separate files.
  • Define one public struct in each child module: Vector, Matrix, and Complex.
  • 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.
Take Quiz

Keep learning

Related questions

View more