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 src/bar.rs.
But main.rs does not declare:
mod foo;
So inside the binary crate, there is no crate::foo.
Now look at src/bar.rs:
use crate::foo;
When bar.rs is compiled as part of the binary crate, Rust tries to find foo in the root of that binary crate. It checks what main.rs declared. Since main.rs only declared mod bar;, there is no foo, so you get the error.
What lib.rs does and does not do
lib.rs exposes modules for the library crate only.
This code:
pub mod foo;
pub mod bar;
creates modules in the library crate's module tree. It does not automatically inject those modules into main.rs.
So lib.rs is not a global module registry for the whole package. It is only the root of the library crate.
Why this matters
Understanding this separation helps you:
- avoid confusing module errors
- organize reusable code in
lib.rs - keep
main.rssmall and focused on starting the program - write binaries that use the library crate cleanly
A very common real-world pattern is:
- put shared logic in
lib.rs - import that library from
main.rs - keep
main.rsmostly for command-line parsing, setup, and calling library functions
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 foo room, the lookup fails.
So the important mental rule is:
crate::...means look inside the current crate's module treemain.rsandlib.rsbuild different trees
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.
Example 2: Fix by declaring both modules in the binary crate
// src/main.rs
mod foo;
mod bar;
fn main() {
println!("Hello");
}
// src/foo.rs
pub fn say_foo() {
println!("foo");
}
// src/bar.rs
use crate::foo;
pub fn bar() {
foo::say_foo();
}
This works because now the binary crate has both foo and bar.
Example 3: Preferred pattern—put shared code in the library crate
A cleaner setup is to define your reusable modules in lib.rs and then use the library from main.rs.
// 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();
}
Here, hello_world is the library crate name taken from Cargo.toml.
This works because:
fooandbarare both part of the library crate- inside
bar.rs,crate::foorefers to the library crate root main.rsuses the library crate as a consumer, just like any other crate would
Important note
If a function should be called from outside its module, it must be pub:
pub fn bar() {
// ...
}
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;
So it includes:
src/foo.rsascrate::foosrc/bar.rsascrate::bar
At this moment, inside bar.rs, crate refers to the library crate.
2. bar.rs imports crate::foo
use crate::foo;
This succeeds because lib.rs declared foo.
3. Cargo builds the binary crate
Rust also builds src/main.rs as a separate crate.
fn main() {
hello_world::bar::bar();
}
The binary crate does not need mod foo; or mod bar; because it is not defining those modules itself. Instead, it is calling into the library crate named hello_world.
4. Program runs
main() calls:
hello_world::bar::bar();
That function calls:
foo::say_foo();
And the output is:
foo
Why your original version breaks
Original main.rs:
mod bar;
This tells Rust to compile bar.rs as part of the binary crate.
Then in bar.rs:
use crate::foo;
Rust looks for foo in the binary crate root. But binary crate root is main.rs, and it never declared mod foo;. So lookup fails.
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]
fn adds_numbers() {
assert_eq!(math::add(2, 3), 5);
}
5. Reusable packages
If you eventually publish a crate, lib.rs defines the API other projects will import. main.rs is only for running your local executable.
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
Internal module references
Inside a library crate, code often uses:
use crate::config::Settings;
This is clean and stable because crate refers to one known root.
Guarding complexity
Real codebases often avoid putting too much logic in main.rs because:
- it is harder to reuse
- it is harder to test directly
- module layout becomes confusing if both
main.rsandlib.rsdefine overlapping modules
Practical advice
If code is shared or reusable, put it under lib.rs.
If code is only for starting the program, keep it in main.rs.
If you find yourself declaring the same modules in both places, pause and ask whether the code should just live in the library crate instead.
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 bar() {
// ...
}
// main.rs
fn main() {
hello_world::bar::bar();
}
This fails because bar() is private.
Fix:
pub fn bar() {
// ...
}
3. Reusing the same file in confusing ways
If both main.rs and lib.rs declare the same module file, you are effectively compiling similar code into two different crates. That can be valid, but it often causes confusion.
Avoid this unless you intentionally want separate crate trees.
4. Misunderstanding crate::
Broken mental model:
crate::means the whole Cargo package
Correct mental model:
crate::means the current crate only
5. Calling library code without crate name from main.rs
Broken code:
fn main() {
bar::bar();
}
This only works if bar is a module in the binary crate.
If bar is defined in lib.rs, use the library crate name:
fn main() {
hello_world::bar::bar();
}
6. Thinking mod and use do the same thing
They do different jobs:
moddeclares and loads a module into the current crateusebrings a path into local scope
Example:
mod foo; // defines module in this crate
use crate::foo; // imports that path into this scope
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() |
| library crate name | Refers to the separate library crate from another crate | External or sibling crate usage | hello_world::bar::bar() |
main.rs vs lib.rs
| File | Role | Crate type | Typical purpose |
|---|---|---|---|
src/main.rs | Entry point for executable | Binary crate | Start program, parse args, call library code |
src/lib.rs | Root of reusable code | Library crate | Expose modules, logic, API |
When to use each approach
| Approach | Good for | Drawback |
|---|---|---|
Declare modules in main.rs | Small binary-only programs | Harder to reuse and test |
Declare modules in lib.rs and call them from main.rs | Most reusable project layouts | Requires understanding separate crates |
| Declare modules in both | Rare special cases | Easy to confuse module paths |
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
// src/main.rs
fn main() {
my_crate::run();
}
Library crate pattern
// src/lib.rs
pub mod foo;
pub fn run() {
foo::do_work();
}
Your error in one line
bar.rs was compiled under main.rs, but main.rs did not declare foo, so crate::foo did not exist.
Best practice
- put reusable logic in
lib.rs - keep
main.rssmall - call library code from
main.rs
Quick fix options
Option 1: Binary-only layout
// main.rs
mod foo;
mod bar;
Option 2: Preferred shared layout
// lib.rs
pub mod foo;
pub mod bar;
// main.rs
fn main() {
hello_world::bar::bar();
}
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 main.rs as a small entry point that calls into the library.
Can a package have both a binary crate and a library crate?
Yes. That is very common in Rust.
Should I declare the same modules in both main.rs and lib.rs?
Usually no. It can work, but it often creates confusion because they belong to different crate trees.
How does main.rs access code from lib.rs?
By using the library crate name, which usually matches the package name from Cargo.toml, such as hello_world::foo::say_foo().
Is use enough to make a module exist?
No. use imports a path that already exists. mod declares the module in the current crate.
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
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.