Error

"module not found" — How to Fix

Fix the 'module not found' error by ensuring the module file exists at the correct path and is properly declared with the mod keyword.

The file exists, but Rust can't see it

You split your Rust project into multiple files to keep things organized. You run cargo build. The terminal flashes red. error[E0583]: file not found for module 'utils'. You check the directory. src/utils.rs is sitting right there. The file exists. The name matches. Rust still refuses to see it.

The compiler isn't broken. The module system has specific rules about where files live and how they connect. Rust doesn't search your entire disk for matching names. It follows a strict path resolution algorithm based on your mod declarations. When the algorithm fails, you get the module not found error. Fixing it requires aligning your file structure with what the compiler expects.

How module resolution works

Think of the module system as a strict library catalog. You can't just slide a book under the counter and expect the librarian to shelve it automatically. You must file a card in the catalog that points to the exact shelf and drawer. If the card says "Shelf A, Drawer 3" but the book is in "Shelf A, Drawer 4", the librarian returns an error.

The mod declaration is the catalog card. The file path is the shelf location. They must align perfectly.

When you write mod name; inside a file, the compiler calculates a search path relative to that file. It looks for two possibilities:

  1. A file named name.rs in the same directory.
  2. A directory named name/ containing a file named mod.rs.

The compiler checks these locations in order. If neither exists, it emits E0583. The search never recurses into unrelated directories. It never guesses. It stops exactly where the declaration tells it to stop.

Minimal example

Here is the simplest working setup. The mod declaration in main.rs points to config.rs. The names match. The paths match.

/// src/main.rs
/// Declares a module named 'config'.
/// Rust searches for src/config.rs or src/config/mod.rs relative to this file.
mod config;

fn main() {
    // Calls the public function inside the config module.
    config::load();
}
/// src/config.rs
/// The module file must exist at this exact path.
/// The 'pub' keyword makes 'load' visible outside this file.
pub fn load() {
    println!("Config loaded.");
}

Run this with cargo run. It compiles and prints the message. The compiler found src/config.rs because mod config; lives in src/main.rs. The relative path is correct.

If you rename src/config.rs to src/configuration.rs, the build fails. The declaration says config. The file says configuration. The catalog card points to the wrong shelf.

Nested modules and directory structure

Real projects grow. You need submodules. Rust supports nesting, but the file structure must reflect the hierarchy.

/// src/main.rs
/// Declares the top-level 'database' module.
/// Rust looks for src/database.rs or src/database/mod.rs.
mod database;

fn main() {
    // Accesses the re-exported function.
    database::connect();
}
/// src/database/mod.rs
/// This file acts as the root for the 'database' module.
/// It re-exports 'connect' so users don't need to type 'database::pool::connect'.
pub use pool::connect;

/// Declares a submodule named 'pool'.
/// Rust looks for src/database/pool.rs relative to this file.
mod pool;
/// src/database/pool.rs
/// Implementation details for the connection pool.
pub fn connect() {
    println!("Connected to database.");
}

This structure uses a directory for database. The mod.rs file defines the module's interface and declares its children. The pool submodule lives in src/database/pool.rs. The path matches the declaration inside mod.rs.

If you move pool.rs to src/pool.rs, the build breaks. The declaration mod pool; is inside src/database/mod.rs. The compiler looks for src/database/pool.rs. It does not look in src/.

The compiler is a path resolver, not a search engine. Give it the exact path.

Common pitfalls and compiler errors

Several patterns trigger E0583 even when the file seems to be in the right place.

Hyphens in filenames

Rust module names use underscores. File names can technically contain hyphens, but the mod declaration must match the module name, not the filename character-for-character if you rely on default resolution.

mod my-utils; is a syntax error. Module names cannot contain hyphens.

mod my_utils; looks for src/my_utils.rs. If your file is named src/my-utils.rs, the compiler won't find it. The file system might allow the hyphen, but the module system expects an underscore.

Rename the file to my_utils.rs. Or use the #[path] attribute to override the default path:

/// src/main.rs
/// Use the 'path' attribute when the filename doesn't match the module name.
/// This is rare but useful for generated code or legacy naming.
#[path = "my-utils.rs"]
mod my_utils;

Case sensitivity

mod Config; looks for src/Config.rs. On Linux, src/config.rs is a different file from src/Config.rs. On Windows, the file system might treat them as the same.

Relying on case-insensitive file systems hides bugs. Your code might compile on your machine but fail on a CI server running Linux. Match the case in the mod declaration to the case in the filename exactly.

Wrong root file

The search path depends on where the mod declaration lives. If you have src/lib.rs and src/main.rs, they are separate roots.

A mod utils; in src/main.rs looks for src/utils.rs. A mod utils; in src/lib.rs also looks for src/utils.rs. They share the same search base because both roots are in src/.

However, if you put mod utils; inside a function, the search path changes. Local modules are allowed but rare. The search path becomes relative to the file containing the function, but the module is scoped to that function. This can confuse the compiler if you expect the module to be visible elsewhere.

The error message tells you where it looked

The compiler output for E0583 includes the paths it tried. Read them.

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

The help message lists the exact files the compiler expects. If your file is at src/configs.rs, the help message shows src/config.rs. The mismatch is obvious. Fix the filename or the declaration.

Treat the error message as a map. It shows the gap between your expectation and the compiler's reality.

Decision: when to use what

Use mod name; with a single file when the module is small and fits comfortably in src/name.rs. Use mod name; with a directory structure when the module contains submodules or grows large enough to warrant splitting into src/name/mod.rs and helper files. Use the #[path = "..."] attribute only when you must reference a file with a name that violates Rust's naming conventions, such as generated code or third-party assets. Reach for use statements to import names into the current scope; mod declares the hierarchy, use imports the identifiers.

Conventions and community norms

The Rust community follows a few conventions around module structure. These aren't enforced by the compiler, but they make codebases easier to navigate.

Prefer src/name/mod.rs for modules with children. Rust 2018 stabilized the ability to use src/name.rs even when the module has submodules, but the community still prefers the directory form for anything with children. If you see src/database/mod.rs, you know database has submodules. If you see src/database.rs, it's likely a leaf module. This visual cue speeds up exploration.

Keep mod declarations at the top of the file. Group them together. This makes the module tree visible immediately. Scattered mod declarations make it hard to see the project structure.

Use pub mod to expose modules to parents. In src/lib.rs, pub mod makes a module part of the crate's public API. In src/main.rs, pub mod exposes the module to external crates that depend on your binary crate, though this is less common. Private mod declarations keep implementation details hidden.

Convention aside: cargo fmt formats every file the same way. Don't argue style; argue logic. The module structure is logic. The formatting is automatic.

Where to go next