Question
I have these four Rust source files:
// src/main.rs
mod bar;
fn main() {
let v = vec![1, 2, 3];
println!("Hello, world!");
}
// src/lib.rs
pub mod foo;
pub mod bar;
// src/foo.rs
pub fn say_foo() {
}
// src/bar.rs
use crate::foo;
fn bar() {
foo::say_foo();
}
When I run cargo run, I get this error:
error[E0432]: unresolved import `crate::foo`
--> src/bar.rs:1:5
|
1 | use crate::foo;
| ^^^^^^^^^^ no `foo` in the root
How should this be fixed? More broadly, how does Rust module lookup work when a project has both main.rs and lib.rs?
I also noticed that adding mod foo; to main.rs fixes the issue, but I do not understand why. I thought lib.rs was the place where modules were exposed. Why does main.rs need its own module declaration too?
For reference, the Cargo.toml is:
[package]
name = "hello-world"
version = "0.1.0"
authors = ["me@mgail.com>"]
edition = "2018"
[dependencies]
Short Answer
By the end of this page, you will understand that src/main.rs and src/lib.rs define two different crate roots in Rust. You will see why crate::foo fails inside code compiled as part of the binary crate, how module declarations are local to each crate, and how to structure shared code so both your binary and library work correctly.
Concept
In Rust, a package can contain multiple crates. The most common case is:
src/main.rs→ a binary cratesrc/lib.rs→ a library crate
Even though both files live in the same package, they are not the same crate.
That is the key idea.
What crate means
Inside Rust code, crate:: always refers to the root of the current crate.
So:
- in
main.rs,cratemeans the binary crate rooted atmain.rs - in
lib.rs,cratemeans the library crate rooted atlib.rs
These roots are separate.
Why your code fails
Your main.rs contains:
mod bar;
That means the binary crate includes a module named bar from .
Mental Model
Think of main.rs and lib.rs as two separate buildings, each with its own front door and room map.
main.rsis one buildinglib.rsis another building
A module declaration like mod bar; adds a room to the building you are currently in.
So if you write mod bar; in main.rs, that creates bar inside the main building.
If you write pub mod foo; in lib.rs, that creates foo inside the library building.
Now imagine code in bar.rs says:
Go to the room
fooin this building.
That is what crate::foo means.
If bar.rs is being used by the binary crate, then crate means the main building. If the main building does not have a room, the lookup fails.
Syntax and Examples
Core syntax
Declaring modules in a crate root
// src/lib.rs
pub mod foo;
pub mod bar;
This tells Rust that the library crate contains modules from src/foo.rs and src/bar.rs.
// src/main.rs
mod bar;
This tells Rust that the binary crate contains a module from src/bar.rs.
These declarations are independent.
Example 1: Why your current setup fails
// src/main.rs
mod bar;
fn main() {
println!("Hello");
}
// src/bar.rs
use crate::foo;
This fails because the current crate is the binary crate, and its root only declares bar, not foo.
Step by Step Execution
Consider this working version:
// src/lib.rs
pub mod foo;
pub mod bar;
// src/foo.rs
pub fn say_foo() {
println!("foo");
}
// src/bar.rs
use crate::foo;
pub fn bar() {
foo::say_foo();
}
// src/main.rs
fn main() {
hello_world::bar::bar();
}
Step-by-step
1. Cargo builds the library crate
Rust starts from src/lib.rs.
It sees:
pub mod foo;
pub mod bar;
Real World Use Cases
1. Command-line applications
A common Rust CLI structure is:
lib.rscontains parsing logic, validation, helpers, and business rulesmain.rshandles argument reading and starts execution
This keeps the app easier to test.
2. Web servers and APIs
In a web app, you might place these in the library crate:
- route handlers
- models
- validation
- database logic
Then main.rs just bootstraps the server.
3. Shared logic across multiple binaries
A package can have several binaries in src/bin/ that all use the same lib.rs.
That is only practical if you understand that the library crate is separate and reusable.
4. Testing
Library code is easier to test because it is designed to be imported.
For example:
// src/lib.rs
pub mod math;
// tests/math_test.rs
use hello_world::math;
#[test]
() {
(math::(, ), );
}
Real Codebase Usage
In real Rust projects, developers usually avoid duplicating module declarations across main.rs and lib.rs unless they truly want separate implementations.
Common pattern: thin main.rs
A very common layout is:
// src/main.rs
fn main() {
my_crate::run();
}
// src/lib.rs
pub mod config;
pub mod app;
pub fn run() {
// orchestrate program logic
}
This has several benefits:
- shared code lives in one place
- tests can target library functions
- binaries stay simple
- module paths are easier to reason about
Visibility patterns
Developers often use:
pub mod x;when exposing a module from the library rootmod x;when the module is internal onlypub fnfor functions that must be accessible from other modules or crates
Common Mistakes
1. Assuming lib.rs and main.rs share one module tree
They do not.
Broken assumption:
// main.rs
mod bar;
// bar.rs
use crate::foo;
// lib.rs
pub mod foo;
Why it breaks:
bar.rsis compiled under the binary cratefooonly exists in the library crate
How to avoid it:
- either declare
fooinmain.rstoo - or, better, move shared modules to
lib.rsand use them frommain.rs
2. Forgetting pub
Broken code:
// bar.rs
fn () {
}
Comparisons
| Concept | What it does | Applies to | Example |
|---|---|---|---|
mod | Declares a module as part of the current crate | Current crate only | mod foo; |
pub mod | Declares a module and makes it public | Current crate only | pub mod foo; |
use | Brings an existing path into scope | Current module scope | use crate::foo; |
crate:: | Refers to the root of the current crate | Current crate only | crate::foo::say_foo() |
Cheat Sheet
Core rule
main.rs and lib.rs are separate crate roots.
What crate:: means
crate::something
Means: start from the root of the current crate.
Module declaration
mod foo;
- declares
fooin the current crate - usually loads code from
foo.rsorfoo/mod.rs
pub mod foo;
- same as
mod foo; - also makes the module public
Importing
use crate::foo;
- only works if
fooexists in the current crate root
Binary crate pattern
FAQ
Why does crate::foo fail even though foo exists in lib.rs?
Because crate:: refers to the current crate only. If bar.rs is compiled from main.rs, then crate means the binary crate, not the library crate.
Does lib.rs automatically expose modules to main.rs?
No. lib.rs defines the library crate's module tree. main.rs is a separate binary crate.
Why did adding mod foo; to main.rs fix the problem?
Because it created foo inside the binary crate, so crate::foo became valid there.
What is the best way to organize code when I have both main.rs and lib.rs?
Usually, put reusable logic in lib.rs and keep as a small entry point that calls into the library.
Mini Project
Description
Build a small Rust package that separates reusable logic into a library crate and uses a binary crate to run it. This demonstrates the correct relationship between lib.rs, main.rs, modules, and crate:: paths.
Goal
Create a package where main.rs calls a public function from the library crate, and one library module calls another using crate::....
Requirements
- Create a
foomodule with a public function that returns or prints a message. - Create a
barmodule that usescrate::foointernally. - Expose both modules from
lib.rs. - Call the
barfunctionality frommain.rsusing the library crate name.
Keep learning
Related questions
Accessing Cargo Package Metadata in Rust
Learn how to read Cargo package metadata like version, name, and authors in Rust using compile-time environment macros.
Associated Types vs Generic Type Parameters in Rust: When to Use Each
Learn when to use associated types vs generic parameters in Rust traits, with clear rules, examples, and practical API design advice.
Convert an Integer to a String in Rust
Learn the current Rust way to convert integers to strings, why `to_str()` no longer works, and when to use `to_string()` or `format!`.