---
title: "13.4 Splitting code into multiple files"
description: "Moving a module into its own file with mod foo and foo.rs, directory modules with mod.rs or named files: the one lesson that replaces C++'s entire header system."
url: "https://learnrust.net/chapter-13/splitting-code-into-files/"
last_updated: "2026-06-13"
---

# 13.4 Splitting code into multiple files

Here's the lesson the chapter has been building toward, the one that replaces six lessons of C++ header machinery (`#include`, header guards, declaration-versus-definition splits, the preprocessor) with a single idea. Once a module grows past a screenful, you'll want it in its own file. In Rust, moving it there is almost nothing: declare the module, and put its body in a file named after it.

## From inline module to file

Start with everything in `main.rs`, the way the last three lessons wrote it:

```rust
// src/main.rs
mod hosting {
    pub fn add_to_waitlist() {
        println!("added to waitlist");
    }
}

fn main() {
    hosting::add_to_waitlist();
}
```

To move `hosting` into its own file, do two things. First, in `main.rs`, replace the module's body with a semicolon:

```rust
// src/main.rs
mod hosting;

fn main() {
    hosting::add_to_waitlist();
}
```

`mod hosting;` (with a semicolon, no braces) tells the compiler: "there's a module called `hosting`; its contents are in another file, go find them." Second, create that file, `src/hosting.rs`, and put the body in it:

```rust
// src/hosting.rs
pub fn add_to_waitlist() {
    println!("added to waitlist");
}
```

That's the whole operation. `mod hosting;` in the parent, `hosting.rs` holding the contents. The compiler looks for `src/hosting.rs` (or, as we'll see, `src/hosting/mod.rs`) automatically based on the module name. Everything else, the paths, `use`, `pub`, the module tree from the last three lessons, works *identically*. Splitting into files changes where the code physically lives, not how it's organized or referred to.

> **Key insight**
>
> `mod hosting;` is a *declaration of where to find a module*, not a textual include. The compiler reads `hosting.rs` once, as a module, with its own scope and privacy. Compare C++'s `#include "hosting.h"`, which literally pastes a file's text into yours and so needs header guards to avoid pasting twice, separate declaration and definition files, and forward declarations to break cycles. Rust has none of that, because `mod` names a module rather than splicing text. The mental model is "this module's body is over there," full stop.

## Directory modules

When a module itself contains submodules, it grows beyond one file and wants a directory. Say `front_of_house` contains `hosting` and `serving`. You make a *directory* named `front_of_house`, and Rust offers two conventions for the directory's own code:

The modern style uses a file named after the module *next to* the directory:

```
src/
├── main.rs
├── front_of_house.rs        ← front_of_house's own code + `pub mod hosting;`
└── front_of_house/
    ├── hosting.rs
    └── serving.rs
```

```rust
// src/main.rs
mod front_of_house;

// src/front_of_house.rs
pub mod hosting;
pub mod serving;

// src/front_of_house/hosting.rs
pub fn add_to_waitlist() { /* ... */ }
```

`main.rs` declares `mod front_of_house;`, found in `front_of_house.rs`. That file in turn declares `pub mod hosting;` and `pub mod serving;`, which the compiler finds inside the `front_of_house/` directory. The module tree is unchanged; only the file layout reflects the nesting now.

The older style puts the module's code in a file called `mod.rs` *inside* the directory (`src/front_of_house/mod.rs`) instead of `front_of_house.rs` beside it. Both work and you'll see both in the wild. The newer `front_of_house.rs` style is generally preferred because a project full of files all named `mod.rs` is hard to navigate in an editor.

> **Best practice**
>
> Prefer the `front_of_house.rs`-beside-the-directory style over `mod.rs`-inside-it. Editors show you a dozen distinct file names instead of a dozen tabs all reading `mod.rs`. Either way, let the module structure drive the file structure: one module per file, nested modules as directories, and the paths in your code stay the same regardless of how you split.

## What didn't change

It's worth dwelling on how little the move disturbed. The call `hosting::add_to_waitlist()` is byte-for-byte the same before and after splitting. `pub` still controls visibility, `use` still shortens paths, `super::` and `crate::` still navigate the same tree. There was no header to write, no declaration to keep in sync with a definition, no build-system file to edit listing the new source. You added one `mod` line and one file. This is the selling point stated plainly: organizing a large Rust project across many files is the same as organizing it within one, plus a `mod` declaration per file.

## Quiz time

**Question #1**

You have an inline module `mod parser { pub fn parse() {} }` in `main.rs` and want it in its own file. What two changes do you make?

<details class="solution">
<summary>Show solution</summary>

(1) In `main.rs`, replace the module body with a declaration: `mod parser;` (semicolon, no braces). (2) Create `src/parser.rs` containing the body: `pub fn parse() {}`. The compiler finds `parser.rs` from the `mod parser;` line. No other change is needed; call sites stay the same.

</details>

**Question #2**

What does `mod parser;` (with a semicolon) tell the compiler, and how does it differ from C++'s `#include`?

<details class="solution">
<summary>Show solution</summary>

It declares that a module named `parser` exists and that its contents live in another file (`parser.rs` or `parser/mod.rs`), which the compiler locates by name. It is not a textual paste: the file is read once as a proper module with its own scope and privacy. C++'s `#include` literally copies a file's text into the current one, which is why it needs header guards, separate declaration/definition, and forward declarations. `mod` needs none of those.

</details>

**Question #3**

A module `network` needs submodules `tcp` and `udp`. Sketch a valid file/directory layout using the preferred style.

<details class="solution">
<summary>Show solution</summary>

```
src/
├── main.rs            (or lib.rs): contains `mod network;`
├── network.rs         contains `pub mod tcp;` and `pub mod udp;`
└── network/
    ├── tcp.rs
    └── udp.rs
```

`network.rs` sits beside the `network/` directory and holds `network`'s own code plus the submodule declarations; the submodule files live inside the directory. (The `mod.rs` alternative would put `network`'s code in `src/network/mod.rs` instead.)

</details>

Files solved, the next two lessons zoom out to the package level: the difference between a binary crate and a library crate (and why having both matters for testing), then a tour of what `Cargo` does beyond `cargo run`.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
