Error E0433

"failed to resolve: use of undeclared crate or module" — How to Fix

This error occurs when the compiler cannot find a crate or module you are trying to use, usually because it is missing from your `Cargo.toml`, not imported correctly, or the module path is wrong.

The compiler cannot find the name you used

You add a dependency to Cargo.toml. You write use serde::Serialize. You hit build. The compiler screams E0433: failed to resolve: use of undeclared crate or module. You check the spelling. It is correct. You check Cargo.toml. The dependency is there. You stare at the screen. The compiler knows something you do not.

E0433 is Rust's way of saying the name you typed does not exist in the current scope. The compiler has a strict rule: every name must be resolvable to a definition before code can use it. If the compiler cannot trace the name back to a valid item, it stops. There is no guessing. There is no fallback. The name is either declared and visible, or it is not.

How name resolution works

Rust builds a module tree for your crate. The module tree is a hierarchy of scopes. Each scope contains names: functions, types, constants, modules, and imports. When you write a name, the compiler searches for it.

The search starts in the current scope. If the name is not found, the compiler looks at use statements. A use statement creates an alias. It points to a name defined elsewhere. The compiler follows the alias to its target. If the target is in another module, the compiler checks visibility. Items are private by default. A private item cannot be accessed from outside its module. If the item is pub, the compiler allows access. If the item is private, the compiler emits E0433.

For external crates, the process is similar but involves Cargo. Cargo reads Cargo.toml, downloads dependencies, and compiles them. The compiler then links your crate to the compiled dependencies. If a crate is missing from Cargo.toml, or if Cargo failed to fetch it, the crate name is unknown. The compiler emits E0433.

Think of the module tree like a building directory. You can only visit rooms that are listed in the directory. You can only enter rooms if the door is unlocked. mod declarations add rooms to the directory. pub unlocks the doors. use statements create shortcuts in the lobby. If you try to walk to a room that is not in the directory, or if the door is locked, you get turned away.

Minimal example: missing module declaration

A common trigger for E0433 is creating a file but forgetting to declare it as a module. Rust does not auto-discover files. The file system is not the module tree. You must explicitly link files to the module tree using mod.

// src/main.rs
// This file exists on disk, but the compiler does not know about it yet.
// Without `mod utils;`, the name `utils` is undefined.

fn main() {
    // E0433: failed to resolve: use of undeclared module `utils`
    utils::helper();
}

The fix is to declare the module in the parent scope. The mod keyword tells the compiler to look for a file named utils.rs or a directory named utils with a mod.rs inside.

// src/main.rs
// Declare the module. The compiler now links src/utils.rs to the module tree.
mod utils;

fn main() {
    // `utils` is now a valid name.
    // Access the function via the module path.
    utils::helper();
}
// src/utils.rs
// Mark the function as public so it can be accessed from `main`.
// Without `pub`, `helper` is private to this module.
pub fn helper() {
    println!("Helper called");
}

Rust requires explicit declarations. If the compiler cannot find the file or the declaration, the name does not exist. Add the mod line to bridge the gap between the file system and the module tree.

Paths and scope navigation

When you reference a name, you can use a relative path or an absolute path. Relative paths start with self, super, or an identifier. Absolute paths start with crate or an external crate name.

self refers to the current module. super refers to the parent module. crate refers to the root of the current crate. Using crate:: is often safer in deep nesting because it anchors the path to the root. If you move the code to a different module, crate:: paths still work. Relative paths might break.

// src/lib.rs
pub mod outer {
    pub mod inner {
        // Use `crate::` to access the root explicitly.
        // This path remains valid even if `inner` moves deeper.
        pub fn get_root_name() -> &'static str {
            crate::ROOT_NAME
        }

        // `super::` goes up one level to `outer`.
        pub fn get_outer_name() -> &'static str {
            super::OUTER_NAME
        }
    }
}

pub const ROOT_NAME: &str = "my_crate";
pub const OUTER_NAME: &str = "outer_module";

Convention aside: the community prefers crate:: paths for cross-module references in larger crates. It makes the origin of the name obvious. Relative paths are fine for local navigation within a module or between immediate siblings.

Realistic example: feature flags and visibility

E0433 often appears when a crate exists but the specific item is hidden behind a feature flag. Many crates split functionality into optional features to reduce compile times and binary size. If you depend on a crate but do not enable the required feature, the compiler cannot see the items guarded by that feature.

# Cargo.toml
[dependencies]
# Missing the `derive` feature.
# The `Serialize` trait is only available when `derive` is enabled.
serde = "1.0"
// src/main.rs
// E0433: failed to resolve: could not find `Serialize` in `serde`
use serde::Serialize;

#[derive(Serialize)]
struct User {
    name: String,
}

The error points to Serialize, not serde. The crate serde is found, but the name Serialize is missing. The fix is to enable the feature in Cargo.toml.

# Cargo.toml
[dependencies]
# Enable the `derive` feature to expose `Serialize` and `Deserialize`.
serde = { version = "1.0", features = ["derive"] }

Another realistic scenario involves workspace members. In a workspace, each crate has its own module tree. You cannot access items from a sibling crate using a relative path. You must use the crate name as the root.

// src/main.rs in crate `app`
// E0433: `my_lib` is not declared.
// `my_lib` is a separate crate in the workspace.
use my_lib::do_work;

fn main() {
    do_work();
}

This code fails because my_lib is not in the dependencies of app. Even though my_lib exists in the workspace, app must explicitly depend on it.

# Cargo.toml in crate `app`
[dependencies]
# Add the workspace crate as a dependency.
# `path` points to the sibling crate.
my_lib = { path = "../my_lib" }

Features are contracts. Read the dependency documentation to find required features. Workspaces are collections of crates, not a single merged crate. Each crate must declare its dependencies explicitly.

Pitfalls and compiler errors

E0433 covers several distinct problems. The error message usually gives a hint, but the fix depends on the root cause.

If the error mentions "undeclared crate", check Cargo.toml. The crate might be missing, misspelled, or behind a feature flag. Run cargo build to ensure Cargo fetches the dependency. If the crate is a local path dependency, verify the path is correct.

If the error mentions "undeclared module", check for a mod declaration. The file might exist, but the parent module might not declare it. Check the file name. Rust matches mod foo; to foo.rs or foo/mod.rs. A mismatch causes E0433.

If the error mentions a type or function inside a module you can see, check visibility. The item might be private. Add pub to the item, or use pub(crate) to expose it within the crate. If you are accessing a nested item, every module in the path must be public. mod a { mod b { fn c() {} } } makes c inaccessible from outside a. You need pub mod a { pub mod b { pub fn c() {} } }.

E0463 is a related error. E0463 means "can't find crate". This happens when the crate file is missing entirely, often due to a broken toolchain or a corrupted Cargo cache. E0433 means the crate might be present, but the name resolution failed. If you see E0463, try cargo clean and rebuild. If E0433 persists, the issue is code, not the build cache.

Convention aside: use pub(crate) instead of pub when you want to expose an item to the rest of the crate but not to external users. It reduces the public API surface and prevents accidental usage from outside. The compiler enforces this boundary, making refactoring safer.

Decision: when to use which approach

Use use statements to bring names into scope when you want to avoid typing long paths. Importing improves readability, but do not over-import. Keep the namespace clean.

Use mod declarations to link files to the module tree when you create a new source file. Rust does not auto-discover files. You must declare every module in its parent.

Use crate:: paths for cross-module references in large crates. Absolute paths anchored to the crate root are stable against refactoring. Relative paths can break when code moves.

Check feature flags when E0433 points to a type or trait inside a crate you already depend on. Many crates hide advanced APIs behind optional features. Enable the feature in Cargo.toml to expose the name.

Check visibility modifiers when E0433 points to a local item. Items are private by default. Add pub or pub(crate) to make the item accessible from the calling scope.

Use cargo clean only when you suspect a stale cache. This is rare. Most E0433 errors are code issues, not cache issues. Clean the cache as a last resort, not a first step.

Rust is explicit. If the compiler cannot find a name, you have not provided enough information. Declare the module, enable the feature, or add the import. The compiler does not guess. Trust the error message. It tells you exactly what is missing.

Where to go next