---
title: How to split modules across files
summary: Split Rust modules across files by creating a new file and declaring it with the `mod` keyword in the parent file.
lang: en
slug: how-to-split-modules-across-files
published: 2026-04-17T12:05:42+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-split-modules-across-files/
---

# How to split modules across files



## When main.rs gets too long

Your `main.rs` started with ten lines. Now it has three hundred. You have a function for parsing config, another for logging, and a third for calculating trajectories. They're all crammed together. Scrolling takes effort. You can't find the function you need. The file feels like a junk drawer.

Rust solves this with modules. Modules let you split code into separate files while keeping a single logical hierarchy. The compiler stitches them back together before building the binary. You get clean files without losing the ability to call code across boundaries.

## Modules are a tree, not just files

Rust's module system is a logical tree. The root is your crate. Everything else hangs off that root. Files are just a convenience for writing code. The compiler maps the module tree to the filesystem, but the tree is what matters.

When you write `mod foo;`, you are telling the compiler: "There is a module named `foo` in my tree." The compiler then looks for a file to fill that module. The file location follows strict rules. Get the file wrong, and the compiler complains. Get the module declaration right, and the structure holds.

Think of the module tree like a directory structure in your head. The files on disk are just the pages where you wrote the content. The `mod` keyword is the table of contents.

## The minimal split

Start with a single file. You want to move a function to its own file.

```rust
// main.rs
mod greetings;

fn main() {
    greetings::say_hello();
}
```

The `mod greetings;` line declares a module. The semicolon is required. This is a declaration, not a block. The compiler sees this and searches for the file.

In the same directory as `main.rs`, create `greetings.rs`.

```rust
// greetings.rs
/// Prints a greeting to stdout.
pub fn say_hello() {
    println!("Hello from a separate file!");
}
```

The function must be `pub`. Without `pub`, the function is private to the module. The parent module (`main`) cannot see it. The compiler enforces this boundary.

Run `cargo run`. The output is `Hello from a separate file!`. The split works.

## How the compiler finds your files

The compiler uses a simple algorithm to locate module files. When it sees `mod foo;` inside a file, it looks for one of two paths relative to that file:

1.  `foo.rs` in the same directory.
2.  `foo/mod.rs` in a subdirectory named `foo`.

The compiler checks `foo.rs` first. If it finds that file, it uses it. If not, it checks for the `foo/mod.rs` structure. If neither exists, compilation fails.

This dual support exists for historical reasons. Older Rust code often used `mod.rs` for every module. Modern Rust prefers `foo.rs` for simple modules and `foo/mod.rs` only when the module has submodules.

Convention aside: Use `foo.rs` for modules that don't have children. Use `foo/mod.rs` when you need to nest submodules inside `foo`. The `mod.rs` file acts as the entry point for that directory.

## Visibility and the pub keyword

Splitting files introduces visibility rules. Every item in Rust is private by default. A function, struct, or constant defined in a module is invisible to the outside world unless you mark it `pub`.

This applies to the module itself too. If you declare `mod utils;` inside another module, `utils` is private to that parent. Code outside the parent cannot access `utils::something`. You need `pub mod utils;` to expose the module.

The visibility chain matters. You can have a public module with private functions. You can have a private module with public functions. The functions are only accessible if the module is accessible.

```rust
// main.rs
pub mod config;

fn main() {
    // This works because config is pub.
    let path = config::get_default_path();
}
```

```rust
// config.rs
/// Returns the default configuration path.
pub fn get_default_path() -> &'static str {
    "/etc/myapp/config.toml"
}

/// Helper function, not exposed outside this file.
fn parse_legacy_format(input: &str) -> String {
    input.to_uppercase()
}
```

`parse_legacy_format` stays hidden. `get_default_path` is exposed. This granularity lets you hide implementation details while exposing a clean API.

Trust the privacy system. If you have to make everything `pub` to get it to work, your module boundaries are likely wrong.

## Ergonomics with use statements

Calling `my_module::sub_module::deep_function()` every time gets tedious. Rust provides `use` statements to bring items into scope.

```rust
// main.rs
mod utils;

fn main() {
    // Verbose path.
    utils::format_date("2024-01-01");

    // Bring the function into scope.
    use utils::format_date;
    format_date("2024-01-01");
}
```

The `use` statement creates a local alias. You can rename items during import.

```rust
use utils::format_date as fmt;

fn main() {
    fmt("2024-01-01");
}
```

You can also glob import everything from a module.

```rust
use utils::*;
```

Glob imports are convenient in tests where you want all helpers available. In library code, they can make it unclear where names come from. Convention aside: Avoid `use *` in production code. Explicit imports make the dependency graph readable.

## Re-exports and flattening APIs

Sometimes you want to expose an item from a submodule without forcing callers to dig through the hierarchy. Use `pub use` to re-export.

```rust
// main.rs
pub mod api;

fn main() {
    // Callers can use api::User directly.
    let user = api::User::new();
}
```

```rust
// api.rs
pub mod models;

// Re-export User so it appears at api::User.
pub use models::User;
```

```rust
// api/models.rs
pub struct User {
    pub name: String,
}

impl User {
    pub fn new() -> Self {
        User { name: String::from("Anonymous") }
    }
}
```

The `User` struct lives in `api/models.rs`. The `pub use` in `api.rs` makes it available as `api::User`. This flattens the API surface. Callers don't need to know about the internal `models` module.

Re-exports are a powerful tool for library design. They let you organize code internally while presenting a simple interface to users.

## Common pitfalls and compiler errors

Module errors are frequent when you start splitting files. The compiler messages are precise. Learn to read them.

### Unresolved import

If you misspell a module name or forget the file, you get `E0432`.

```text
error[E0432]: unresolved import `my_module`
  --> main.rs:1:5
   |
1  | mod my_module;
   |     ^^^^^^^^^ no external crate `my_module`
```

Check the file name. Ensure `my_module.rs` exists in the same directory. Check for typos in the `mod` declaration.

### Private item access

If you try to call a non-`pub` function from another module, the compiler rejects you with `E0603`.

```text
error[E0603]: function `helper` is private
  --> main.rs:4:18
   |
4  |     utils::helper();
   |                  ^^^^^^ private function
```

Add `pub` to the function definition. Or reconsider if the function should be exposed.

### Module not found in path

If you declare `mod foo;` but the file is in the wrong place, the compiler says it can't find the module.

```text
error[E0583]: file not found for module `foo`
  --> main.rs:1:1
   |
1  | mod foo;
   | ^^^^^^^^
   |
   = help: to create the module `foo`, create file "src/foo.rs" or "src/foo/mod.rs"
```

The help message tells you exactly where to put the file. Follow it.

### Use of undeclared type

If you forget to import a type with `use`, you get `E0412`.

```text
error[E0412]: cannot find type `Config` in this scope
  --> main.rs:5:22
   |
5  |     let c = Config::new();
   |                      ^^^^^^ not found in this scope
```

Add `use my_module::Config;` or use the full path `my_module::Config`.

Treat compiler errors as instructions. They rarely lie. Fix the error, and the code moves forward.

## Decision matrix

Use `foo.rs` when the module is simple and has no submodules. This keeps the file structure flat and easy to navigate. Use `foo/mod.rs` when the module contains submodules. The directory structure mirrors the module hierarchy, making it clear that `foo` is a container. Use `pub use` when you want to expose an item from a deeper submodule at a shallower level. This flattens the API and reduces boilerplate for callers. Use `use` statements to bring frequently used items into local scope. This improves readability without changing visibility. Use `pub mod` when you want other modules or crates to access the module. Use plain `mod` when the module is internal implementation detail.

## Where to go next

- [How to Create Modules in Rust with mod](/en/how-to-create-modules-in-rust-with-mod/)
- [How to Create an Internal (Private) Module in Rust](/en/how-to-create-an-internal-private-module-in-rust/)
- [How Does the Module System Work in Rust?](/en/how-does-the-module-system-work-in-rust/)