Error

"unresolved import" — How to Fix

Fix the unresolved import error by adding a use statement or marking the item as public.

You split your growing Rust project into separate files

You create src/parser.rs, write a function, and head back to main.rs to call it. You type use parser::parse_input; and hit build. The compiler immediately stops you with an unresolved import error. The file exists. The function exists. The path looks right. So why does Rust refuse to connect them?

How the module tree actually works

Rust organizes code into a strict module tree. Every file, every mod block, and every crate sits at a specific coordinate in that tree. The use keyword does not create new code. It creates a local alias for an existing path. If the compiler cannot trace that path from your current file to the target item, it rejects the import.

Think of the module system like a corporate directory. Just because a department exists does not mean you can dial their extension. You need the correct routing number, and the department must have published its contact information. In Rust, pub publishes the item. use dials the number. If either piece is missing, the call fails.

Visibility is a wall, not a suggestion. Mark it pub or keep it locked.

Minimal example that triggers the error

Here is the simplest case that produces the failure. You have two files in your src directory.

// src/utils.rs
/// Calculates the discount for a given price.
/// This function is private by default, so it cannot be imported elsewhere.
fn calculate_discount(price: f64) -> f64 {
    price * 0.9
}
// src/main.rs
// Declares the module so the compiler knows the file exists.
mod utils;

// Attempts to create a local alias for the private function.
use utils::calculate_discount;

fn main() {
    // This line will never run because the import fails at compile time.
    let total = calculate_discount(100.0);
    println!("Final price: {}", total);
}

The compiler rejects this with E0432 (unresolved import). The error points directly to the use line. The problem is not the path. The problem is visibility. calculate_discount lacks the pub keyword, so it is invisible outside utils.rs. Adding pub fn to the definition fixes it instantly.

What happens behind the scenes

When you run cargo build, the compiler constructs a module tree before it type-checks a single function. It starts at your crate root (main.rs or lib.rs) and follows every mod declaration. Each mod becomes a node in the tree. When it encounters a use statement, it attempts to resolve the path against that tree.

The resolution process checks three things in order. First, it verifies the path syntax. Second, it walks the tree to find the target module or item. Third, it checks visibility boundaries. If the item is private, the walk stops at the module wall. If the path contains a typo, the walk hits a dead end. If the target lives in a different crate, the walk checks your Cargo.toml dependencies.

The compiler does not guess. It follows a strict graph. Fix the path or open the door.

Realistic example with nested modules

Real projects rarely stay flat. You will quickly nest modules inside folders. Consider a library crate with a database module.

// src/lib.rs
// Exposes the db module to external crates.
pub mod db;
// src/db/mod.rs
// Marks child modules as public so they can be traversed.
pub mod connection;
pub mod models;
// src/db/connection.rs
/// Manages a pool of database connections.
/// Marked pub so it can be imported from outside this file.
pub struct ConnectionPool {
    // fields omitted for brevity
}

impl ConnectionPool {
    /// Creates a new pool with the given size.
    /// Public constructor matches the public struct.
    pub fn new(size: usize) -> Self {
        Self { /* ... */ }
    }
}

From main.rs or another module, you can import the pool using the full path.

// src/main.rs
// Anchors the path to the crate root to avoid ambiguity.
use crate::db::connection::ConnectionPool;

fn setup() {
    // The path resolves to the struct inside the nested module.
    let pool = ConnectionPool::new(10);
}

Typing the full path every time gets tedious. You can shorten it with a use statement, or you can re-export it at a higher level so downstream code does not need to know your internal folder structure.

// src/db/mod.rs
pub mod connection;
pub mod models;

// Re-exports the pool so users can write `db::ConnectionPool` instead.
// This flattens the public API without moving files.
pub use connection::ConnectionPool;

Convention aside: the Rust community prefers pub use for flattening public APIs. It keeps your internal directory structure flexible without breaking external code that depends on your crate. Group your use statements at the top of the file, external crates first, then internal modules. Run cargo fmt to enforce the spacing.

Visibility rules that block imports

Understanding visibility modifiers prevents half of these errors. Rust provides three main levels.

pub exposes the item to any crate that depends on yours. Use it for your public API.

pub(crate) exposes the item to any file within the same crate. External crates cannot see it. This is the standard guard for library internals. It keeps helper functions accessible across your codebase while hiding implementation details from users.

pub(super) exposes the item only to the parent module. It is useful when you want a child module to share a type with its parent, but keep it hidden from siblings or the crate root.

If you mark a struct pub but leave its fields private, external code can see the type name but cannot access the fields. You will get an unresolved import for the type, followed by field visibility errors once the import resolves. Mark both the type and the fields according to your intended access level.

Common pitfalls and how to spot them

Unresolved imports usually stem from one of four patterns. Recognizing the pattern saves you from staring at the wrong file.

The first pattern is a missing pub on a parent module. You mark a function pub, but the module containing it is private. The compiler stops at the module boundary. You must mark every step up the tree as pub or pub(crate) if you want external access.

The second pattern is a typo in the path. Rust does not fuzzy-match module names. use crate::databse::Pool fails even if database exists. The compiler will suggest similar names if they are close enough, but it will not auto-correct.

The third pattern is cross-crate confusion. You try to use some_crate::Item, but you never added some_crate to your Cargo.toml. The compiler only knows about crates explicitly declared as dependencies. Add the dependency, run cargo build, and the path resolves.

The fourth pattern is mixing mod and use. mod declares a module and tells the compiler where to find its source file. use creates a local alias. You cannot replace mod with use to load a file. You need both: mod to register the file in the tree, and use to bring items into your current scope.

Read the error line number. It points to the exact use statement that broke. Fix the path, not the compiler.

When to use which import pattern

Match your import strategy to your crate's architecture. Consistency beats cleverness.

Use use when you want to shorten a long path for local code. It keeps your function bodies clean without changing visibility. Use pub use when you want to re-export an item from a parent module so downstream code does not need to know your internal folder structure. Use crate:: when you are inside a library crate and want to anchor the path to the crate root. It prevents ambiguity when multiple modules share the same name. Use super:: when you need to step up one level in the module tree. It is relative to the current file, not the project root. Reach for pub on the item itself when it needs to be visible outside its immediate module. Without it, the item stays locked. Reach for pub(crate) when you want to expose an item to other files in the same crate while hiding it from external users. It is the standard guard for library internals.

Where to go next