Question
I created a Rust library crate with:
cargo new my_lib
and I want to use that library from a separate Rust binary project:
cargo new my_program --bin
In my program, I tried to write:
extern crate my_lib;
fn main() {
println!("Hello, World!");
}
What do I need to do to make this work?
The two crates are not inside the same project folder structure beyond being siblings:
.
├── my_lib
└── my_program
I expected I could override the path based on the Cargo guide, but I found a note saying:
You cannot use this feature to tell Cargo how to find local unpublished crates.
This is with a stable Rust toolchain.
Short Answer
By the end of this page, you will understand how Rust and Cargo connect one crate to another using dependencies in Cargo.toml. You will learn how to use a local unpublished crate with a path dependency, how Cargo resolves it, what folder structure is allowed, and how this differs from crates published on crates.io.
Concept
In Rust, one crate does not automatically know about another crate just because both folders exist on your machine. Cargo only includes crates that are explicitly declared as dependencies.
For a local unpublished crate, the usual solution is a path dependency. That means your application tells Cargo, “this dependency lives in this folder on disk.”
For example, if my_program depends on my_lib, you declare that relationship in my_program/Cargo.toml.
[dependencies]
my_lib = { path = "../my_lib" }
This matters because Cargo is a build system and package manager. It needs a reliable, explicit way to know:
- which crates your project depends on
- where to find them
- when to rebuild them
- which versions or sources to use
A local crate can be used even if it is unpublished, as long as:
- it is a valid Cargo package
- it has a
Cargo.toml - the path points to the correct folder
In modern Rust, once the dependency is declared, you usually do not need extern crate in most cases. You can import items directly with use, or refer to them by crate name.
The important idea is this:
- Rust code uses crate names
- Cargo decides where those crates come from
Mental Model
Think of Cargo as a delivery manager for your project.
Your Rust code says, “I need my_lib.” But Cargo asks, “Where should I get it from?”
There are several possible answers:
- from crates.io
- from a Git repository
- from a local folder on disk
A path dependency is like giving Cargo a street address:
my_lib = { path = "../my_lib" }
Now Cargo knows exactly where to pick up that crate.
Without that address, writing use my_lib::... or extern crate my_lib; is like asking for a package without telling the delivery manager where it lives.
Syntax and Examples
Basic syntax
In the dependent crate's Cargo.toml:
[dependencies]
my_lib = { path = "../my_lib" }
This says:
- dependency name:
my_lib - source: the local folder
../my_lib
Example project structure
.
├── my_lib
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
└── my_program
├── Cargo.toml
└── src
└── main.rs
Library crate
my_lib/src/lib.rs
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
Program crate
my_program/Cargo.toml
[package]
name =
=
=
= { path = }
Step by Step Execution
Consider this code:
my_lib/src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
my_program/Cargo.toml
[dependencies]
my_lib = { path = "../my_lib" }
my_program/src/main.rs
use my_lib::add;
fn main() {
let result = add(2, 3);
println!("{}", result);
}
Step by step
- You run:
cargo run
from inside my_program.
Real World Use Cases
Local unpublished crates are very common in real Rust development.
Shared internal libraries
A team may create a reusable internal crate for:
- validation helpers
- logging utilities
- configuration loading
- domain models
That crate may never be published publicly.
Multi-project development
You might build:
- one crate for business logic
- one binary crate for a CLI
- one binary crate for a server
Both binaries can depend on the same local library.
Prototyping before publishing
Many developers start with a local crate first. Once the API stabilizes, they may publish it later.
Monorepo-style setups
Even if crates are in separate folders, local path dependencies let you develop them together without publishing every change.
Testing a library in a real app
A path dependency is useful when you want to try a new library feature immediately in another project before releasing a version.
Real Codebase Usage
In real codebases, developers usually combine local crates with a few common patterns.
Reusable modules extracted into crates
When code becomes shared across multiple binaries or services, teams often move it into a local crate instead of copying files between projects.
Clear public API design
A library crate usually exposes only what consumers need:
pub fn parse_config() {}
and keeps internal helpers private.
Validation and domain logic
A local crate might centralize rules so multiple apps behave the same way.
For example:
- input validation
- pricing calculations
- serialization helpers
- authentication utilities
Incremental development
During development, path dependencies are convenient because changes in the library are picked up when you rebuild the dependent crate.
Workspaces as a next step
In larger projects, developers often move related crates into a Cargo workspace. A workspace is not required for path dependencies, but it helps when:
- building multiple crates together
- sharing
Cargo.lock - running tests across several crates
Common pattern: binary + library
A binary crate often stays small and delegates most logic to a library crate:
Common Mistakes
1. Forgetting to add the dependency to Cargo.toml
Writing this in Rust code is not enough:
use my_lib::greet;
If Cargo.toml does not include my_lib, Cargo cannot resolve it.
Fix
[dependencies]
my_lib = { path = "../my_lib" }
2. Using the wrong path
Broken example:
[dependencies]
my_lib = { path = "./my_lib" }
If my_program and my_lib are sibling folders, ./my_lib points to the wrong place.
Fix
[dependencies]
my_lib = { path = "../my_lib" }
3. Expecting folder names alone to make crates visible
Just because this exists:
Comparisons
| Approach | Where the crate comes from | When to use it | Example |
|---|---|---|---|
| Path dependency | Local filesystem | Local development, internal crates, testing unpublished code | my_lib = { path = "../my_lib" } |
| crates.io dependency | Public registry | Stable public packages | serde = "1.0" |
| Git dependency | Remote Git repository | Using code not yet published, or private hosted code | my_lib = { git = "https://github.com/user/my_lib" } |
extern crate vs use
| Style | Rust era |
|---|
Cheat Sheet
# my_program/Cargo.toml
[dependencies]
my_lib = { path = "../my_lib" }
// my_program/src/main.rs
use my_lib::some_function;
fn main() {
some_function();
}
Rules to remember
- Local crates must be declared in
Cargo.toml - The
pathis relative to the crate containing thatCargo.toml - The library crate should usually contain
src/lib.rs - Functions, structs, and modules must be
pubto be used from another crate - In modern Rust,
extern crateis usually unnecessary
Common path example
If the structure is:
.
├── my_lib
└── my_program
then inside my_program/Cargo.toml use:
my_lib = { path = }
FAQ
Can I use a local crate without publishing it to crates.io?
Yes. Add it as a path dependency in Cargo.toml.
Do the two crates need to be in the same folder?
No. They can be anywhere, as long as the path points to the correct location.
Why does extern crate my_lib; not work by itself?
Because Rust code does not tell Cargo where to find the crate. You must declare the dependency in Cargo.toml.
What path should I use for sibling folders?
If my_program and my_lib are siblings, use:
my_lib = { path = "../my_lib" }
Does a local crate need lib.rs?
If you want to use it as a library dependency, yes, it should provide a library target, usually through src/lib.rs.
Should I use extern crate in modern Rust?
Usually no. In Rust 2018 and later, use statements are typically enough once the dependency is declared.
When should I use a workspace instead of just a path dependency?
Mini Project
Description
Create a small Rust setup where one local library crate provides reusable text utilities and a separate binary crate uses it through a path dependency. This demonstrates the exact workflow for local unpublished crates in a practical way.
Goal
Build a binary program that imports and uses functions from a local library crate stored in a different folder.
Requirements
- Create a library crate named
text_tools - Add at least one public function in
src/lib.rs - Create a binary crate named
app - Add
text_toolsas a path dependency inapp/Cargo.toml - Call the library function from
app/src/main.rs
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!`.