Error E0432

"unresolved import" — How to Fix

Fix Rust E0432 by ensuring the module is declared and the imported item is public.

The file exists, but Rust can't see it

You just wrote a helper function in utils.rs. You add use utils::helper; to main.rs and hit run. The compiler throws E0432: "unresolved import". You stare at the file. The function is public. The filename matches. The path looks right. Rust is lying to you. Or rather, Rust is following a rule you haven't met yet.

The compiler doesn't know the module exists because you haven't told the parent scope about it. E0432 is the compiler's way of saying the module tree doesn't have the branch you're pointing at. The file might be sitting right there on disk, but until you declare it, it's invisible to the code.

Modules, visibility, and the tree

Rust organizes code into a module tree. The root of the tree is the crate. Every mod declaration adds a branch to that tree. A use statement is just a shortcut to navigate the tree. It creates a local alias for a path. If the tree doesn't have the branch you're pointing at, the shortcut fails.

You need two things for an import to work. First, the module must be declared in the parent scope so the compiler knows it exists. Second, the item inside must be pub so you're allowed to see it. E0432 means one of those links is broken. The compiler builds the tree by reading mod declarations. It does not scan your directory for .rs files and guess what you want. If you skip the declaration, the file is ignored.

Think of the module tree like a building map. mod declarations draw the rooms on the map. use statements are shortcuts you write on sticky notes to find a room faster. If the room isn't on the map, the sticky note is useless. If the room is on the map but the door is locked, you still can't get in.

Minimal example

This example shows the most common cause of E0432. The file exists, but the module is never declared.

// src/main.rs
// This line fails. The compiler hasn't seen `mod utils;` yet.
// use utils::greet; // E0432: unresolved import `utils`

// Declare the module. This tells the compiler to look for src/utils.rs.
mod utils;

// Now the path exists. The import resolves.
use utils::greet;

fn main() {
    greet();
}
// src/utils.rs
/// Prints a greeting to the console.
pub fn greet() {
    println!("Hello from utils!");
}

Convention aside: Rust used to require mod.rs files for modules. Modern Rust (since the 2018 edition) prefers one file per module. If you have mod utils;, the compiler looks for src/utils.rs. If you see src/utils/mod.rs in an old project, it still works, but new code should use the single-file style. Stick to src/utils.rs.

What happens under the hood

When the compiler sees mod utils;, it pauses. It checks the filesystem relative to the current file. It finds src/utils.rs. It parses that file and adds utils to the current scope. Now use utils::... can resolve.

If you skip the mod declaration, the compiler never looks at the file. The file might as well not exist. The use statement tries to find utils in the current scope, finds nothing, and emits E0432.

Visibility is the second gate. If utils.rs contains fn greet(), the function is private to the module. Even if the module is declared, use utils::greet; fails because greet isn't pub. The compiler sees the module but can't see inside. The error message will still be E0432 because the import is unresolved from your perspective. The item is there, but you don't have permission to use it.

The compiler resolves paths from the use statement outward. It checks the current scope, then the parent, then the crate root. If you write use crate::utils::greet;, it starts at the root and walks down. If you write use utils::greet;, it starts at the current scope. The result is the same if the module is declared locally, but the absolute path is safer when you move code around.

Realistic example

Real projects have nested modules. This example shows a models module containing a user submodule. Every link in the chain must be declared and public.

// src/main.rs
// Declare the top-level module.
mod models;

// Import User from the nested module.
// The path is crate::models::user::User.
use models::user::User;

fn main() {
    let user = User::new("Alice");
    println!("{}", user.name);
}
// src/models.rs
/// Contains data structures for the application.
// Declare the submodule. This looks for src/models/user.rs.
pub mod user;
// src/models/user.rs
/// Represents a user in the system.
pub struct User {
    pub name: String,
}

impl User {
    /// Creates a new User with the given name.
    pub fn new(name: &str) -> Self {
        Self {
            name: name.to_string(),
        }
    }
}

Here models is a module. Inside it, user is a submodule. The path is crate::models::user::User. The use statement shortcuts that. Note the pub mod user; inside models.rs. Without that, user is private to models, and you can't import from it outside. The pub on mod makes the submodule accessible to code outside models.

Check the pub chain. If any link in the path is private, the import breaks.

Pitfalls and debugging

E0432 usually comes down to three causes. Missing mod declaration is the most common. You have the file, but no mod in the parent. Private visibility is the second. The item exists, but it's not pub. Path confusion is the third. You used the wrong path or mixed up relative and absolute imports.

Start by checking the parent scope for a mod declaration. If the module is inline, make sure the block is there. If it's a file, make sure the mod line exists in the parent file. Next, verify the item has pub visibility. Check every module in the path. If user is inside models, and models is private, you can't reach user. Finally, trace the path from the root. Use crate:: to be explicit. If the compiler suggests a similar name, check for typos.

The compiler emits E0432 with a message like "unresolved import foo::bar". It often suggests "maybe you meant foo::baz?" if there's a typo. Pay attention to the suggestion. It's usually right.

Convention aside: Run cargo check before cargo run. cargo check compiles the code without linking, which is faster. You'll catch E0432 errors quicker during development.

Inline modules and re-exports

Not every module needs a file. You can define a module inline in the same file. This is useful for small groups of related items that don't justify a separate file.

// src/main.rs
/// Configuration settings for the application.
mod config {
    /// The default port number.
    pub const DEFAULT_PORT: u16 = 8080;

    /// The default host address.
    pub const DEFAULT_HOST: &str = "127.0.0.1";
}

fn main() {
    println!("Host: {}, Port: {}", config::DEFAULT_HOST, config::DEFAULT_PORT);
}

Inline modules are declared with mod name { ... }. They behave exactly like file-based modules. You can import from them, and they can have private items. Use inline modules when the code is short and tightly coupled to the parent. Use file-based modules when the code grows or needs to be shared across the crate.

Libraries often use pub use to re-export items. This lets you expose a clean API without forcing users to navigate deep paths.

// src/lib.rs
mod internal {
    pub mod helpers {
        /// A helper function for parsing.
        pub fn parse_input(input: &str) -> Result<String, &str> {
            Ok(input.trim().to_string())
        }
    }
}

// Re-export the helper at the crate root.
// Users can now write `use my_crate::parse_input;`
pub use internal::helpers::parse_input;

Re-exports create an alias at the current scope. The item is still defined in the original module, but it's accessible from the new path. This is a standard pattern in library crates. It keeps the internal structure flexible while providing a stable public API.

When to use what

Use mod name; when you want to introduce a new module from a file. This builds the tree and tells the compiler to load the file. Use mod name { ... } when you want to define a module inline. This is good for small, self-contained groups of items. Use use path::to::item; when you want a shorthand for a long path. This does not load the file; it just creates an alias. Use pub on items when you want them accessible from outside the current module. Use pub(crate) when you want items visible across the crate but hidden from external users. This is the safe default for internal helpers. Use pub(super) when you only need visibility in the parent module. Use use crate::... for absolute paths from the root. This makes refactoring safer because the path doesn't change if you move the file. Use use super::... for relative paths up one level. Reach for this only in deep nesting where absolute paths are verbose.

Prefer pub(crate) over pub. Expose only what the world needs.

Where to go next