The Cargo Guide
Project Creation and Scaffolding
What Project Creation and Scaffolding Mean in Cargo
In Cargo, project creation is the process of creating a new Rust package with a valid Cargo.toml manifest and an initial source layout. Scaffolding is the starting structure Cargo creates so that the package is immediately buildable and follows standard Rust conventions.
The core tools are:
cargo new: create a new package in a new directorycargo init: initialize a package inside an existing directory
These commands do more than make files. They establish a package identity, choose whether the package is a binary or library by default, optionally create version control metadata, and set up a layout that Cargo can understand immediately.
A useful mental model is:
cargo newis for starting a new project foldercargo initis for turning an existing folder into a Cargo package
Starting a New Project with cargo new
The most common starting point is cargo new.
cargo new hello_appThis creates a new binary package named hello_app.
Typical result:
hello_app/
āāā Cargo.toml
āāā src/
āāā main.rsGenerated manifest:
[package]
name = "hello_app"
version = "0.1.0"
edition = "2024"
[dependencies]Generated source:
fn main() {
println!("Hello, world!");
}You can build and run it immediately:
cd hello_app
cargo runThis matters pedagogically because Cargo does not leave the learner with an empty or ambiguous state. It creates a working Rust package from the start.
When to Use cargo init Instead
Use cargo init when you already have a directory and want to turn it into a Cargo package.
Example:
mkdir notes_tool
cd notes_tool
cargo initThat produces a package in the current directory rather than creating a new subdirectory.
Result:
notes_tool/
āāā Cargo.toml
āāā src/
āāā main.rsThis is especially useful when:
- you already created the repository directory manually
- you cloned an empty repository and want to add Cargo
- you have an ad hoc Rust project and want to formalize it
A common distinction is:
cargo new my_app # creates ./my_app/
cargo init # initializes the current directoryDefault Behavior: Binary Packages
By default, both cargo new and cargo init create a binary package unless told otherwise.
cargo new calculatorThis creates src/main.rs, which means the package builds an executable.
fn main() {
println!("Calculator starting...");
}This default is a good match for beginners because executable programs are easy to run and inspect.
cargo runThe learner sees a quick feedback loop: create, run, modify, rerun.
Choosing --bin vs --lib
Cargo lets you choose whether the initial package is a binary or a library.
Explicit binary package:
cargo new cli_tool --binExplicit library package:
cargo new math_utils --libA library package uses src/lib.rs instead of src/main.rs.
Example layout:
math_utils/
āāā Cargo.toml
āāā src/
āāā lib.rsGenerated library source might look like this:
pub fn add(a: i32, b: i32) -> i32 {
a + b
}The decision between --bin and --lib is really about intent:
- choose
--binwhen the package is primarily a runnable program - choose
--libwhen the package is primarily reusable code
This is not permanent. A package can later grow to contain both a library and one or more binaries.
How to Think About Package Naming
Package naming matters early because Cargo uses the package name in several places.
Example:
cargo new weather_reporterManifest:
[package]
name = "weather_reporter"
version = "0.1.0"
edition = "2024"
[dependencies]In Rust code, a package with a library target is typically referenced by its crate name. For example:
pub fn summarize() -> String {
"Sunny".to_string()
}Then elsewhere:
fn main() {
println!("{}", weather_reporter::summarize());
}Practical naming guidance:
- use lowercase names
- prefer clear, concise names
- use underscores rather than spaces or punctuation
- choose a name that still makes sense if the project grows
A poor name creates friction later in code, commands, repository structure, and publication.
Edition Selection
Cargo writes a Rust edition into Cargo.toml when it creates a package.
[package]
name = "hello_app"
version = "0.1.0"
edition = "2024"
[dependencies]The edition controls certain language-level defaults and compatibility behavior. For a learner, the important point is that the edition is part of the package definition and should usually be left at the current stable default unless there is a specific reason to change it.
If needed, you can edit it manually:
[package]
name = "legacy_demo"
version = "0.1.0"
edition = "2021"
[dependencies]A beginner-friendly rule is:
- use the current edition for new learning projects
- only change the edition deliberately, not casually
This keeps examples aligned with modern Rust style and tooling behavior.
VCS Initialization
Cargo can initialize version control metadata when creating a project. In many environments, cargo new will create a Git repository automatically if that behavior is enabled and suitable for the context.
You can also control this explicitly.
Create a project and force Git initialization:
cargo new inventory_app --vcs gitDisable version control initialization:
cargo new scratchpad --vcs noneThis matters because scaffolding is not just about source files. For real development, project structure often includes source control from the beginning.
Example layout with Git initialized:
inventory_app/
āāā .git/
āāā .gitignore
āāā Cargo.toml
āāā src/
āāā main.rsFor learning purposes, the main takeaway is that Cargo can scaffold both the Rust package and some of the surrounding project hygiene.
Initial Layout and Why It Matters
Cargo's initial layout is small on purpose.
Binary package:
my_app/
āāā Cargo.toml
āāā src/
āāā main.rsLibrary package:
my_lib/
āāā Cargo.toml
āāā src/
āāā lib.rsThis minimal structure teaches a strong lesson: Cargo relies on convention over configuration. The learner does not need to define every path manually.
For example, Cargo knows:
src/main.rsis the default binary targetsrc/lib.rsis the default library target
That lets a beginner focus on package intent and code, not on boilerplate configuration.
Creating a Package Inside an Existing Repository
A very common real-world case is an existing Git repository that does not yet contain a Cargo package.
Example:
mkdir task_tracker
cd task_tracker
git init
cargo init --binResult:
task_tracker/
āāā .git/
āāā .gitignore
āāā Cargo.toml
āāā src/
āāā main.rsThis is often the right choice when:
- the repository already exists on GitHub or another host
- the project began as notes, scripts, or design docs
- a team agreed on a repository first and Rust came later
cargo init is the bridge from "folder" to "Rust package."
Converting Ad Hoc Rust Code into a Cargo Package
Suppose you started with a standalone Rust file:
scratch/
āāā hello.rsExample file:
fn main() {
println!("Hello from an ad hoc file");
}You can compile it directly with rustc:
rustc hello.rs
./helloBut once the project grows, that workflow becomes limiting. To convert it into a Cargo package:
mkdir hello_project
mv hello.rs hello_project/
cd hello_project
cargo init --binThen replace the generated src/main.rs with your existing code:
fn main() {
println!("Hello from an ad hoc file");
}Or move the file into place:
mv hello.rs src/main.rsNow the project has a proper manifest and can use Cargo workflows:
cargo run
cargo test
cargo buildThis is an important transition because many learners start with loose files, but Cargo becomes more valuable as soon as the code needs dependencies, tests, or multiple targets.
Turning Existing Reusable Code into a Library Package
Sometimes the existing code is not really an application. It is reusable logic.
Suppose you have this file:
pub fn slugify(s: &str) -> String {
s.to_lowercase().replace(' ', "-")
}To turn that into a library package:
mkdir text_tools
cd text_tools
cargo init --libThen place the code in src/lib.rs:
pub fn slugify(s: &str) -> String {
s.to_lowercase().replace(' ', "-")
}You can test it with a unit test:
#[cfg(test)]
mod tests {
use super::slugify;
#[test]
fn converts_spaces_to_hyphens() {
assert_eq!(slugify("Hello World"), "hello-world");
}
}Then run:
cargo testThis reinforces the idea that scaffolding is not only about making empty projects; it is also about giving existing code the right package shape.
Growing from the Initial Scaffold to a Multi-Target Package
A package created with cargo new often starts with a single binary or library target, but it can grow naturally.
Start with:
cargo new data_tool
cd data_toolInitial layout:
data_tool/
āāā Cargo.toml
āāā src/
āāā main.rsAdd a library target:
// src/lib.rs
pub fn normalize_name(name: &str) -> String {
name.trim().to_lowercase()
}Update the binary to use it:
// src/main.rs
fn main() {
let value = data_tool::normalize_name(" Alice ");
println!("{value}");
}Add another binary:
// src/bin/report.rs
fn main() {
println!("{}", data_tool::normalize_name(" BOB "));
}Run the additional binary:
cargo run --bin reportThe key insight is that the initial scaffold is not a cage. It is a starting point that can evolve into a more capable package structure.
A Practical Multi-Target Layout
Here is a meaningful package layout that grows from simple scaffolding into a more complete development shape:
reporting_tool/
āāā Cargo.toml
āāā src/
ā āāā lib.rs
ā āāā main.rs
ā āāā bin/
ā āāā export.rs
āāā examples/
ā āāā quickstart.rs
āāā tests/
āāā formatting.rsLibrary logic:
// src/lib.rs
pub fn format_title(s: &str) -> String {
s.trim().to_uppercase()
}Main binary:
// src/main.rs
fn main() {
println!("{}", reporting_tool::format_title(" monthly report "));
}Additional binary:
// src/bin/export.rs
fn main() {
println!("export: {}", reporting_tool::format_title(" annual summary "));
}Example:
// examples/quickstart.rs
use reporting_tool::format_title;
fn main() {
println!("{}", format_title(" draft report "));
}Integration test:
// tests/formatting.rs
use reporting_tool::format_title;
#[test]
fn trims_and_uppercases() {
assert_eq!(format_title(" draft report "), "DRAFT REPORT");
}Useful commands:
cargo run
cargo run --bin export
cargo run --example quickstart
cargo testThis shows how a project can begin with standard scaffolding and grow into a package with reusable code, multiple executables, examples, and tests.
Common Mistakes During Project Creation
One common mistake is choosing --bin when the real goal is reusable code. Another is choosing --lib when the learner primarily wants a runnable CLI program.
A second mistake is creating Rust files manually without a manifest, then delaying the move to Cargo too long.
A third mistake is misunderstanding where files belong.
For example, this is correct:
src/main.rs
src/lib.rs
src/bin/helper.rs
examples/demo.rs
tests/integration_test.rsBut beginners sometimes create files in arbitrary places and then wonder why Cargo does not discover them.
The deeper lesson is that scaffolding works best when the project stays aligned with Cargo's conventions.
Hands-On Learning Sequence
A good progression for practice is to create several packages with different intentions.
First, create a binary package:
cargo new hello_cli --bin
cd hello_cli
cargo runThen create a library package:
cd ..
cargo new string_tools --lib
cd string_tools
cargo testThen initialize a package in an existing folder:
cd ..
mkdir notes_app
cd notes_app
cargo init --bin
cargo runThen grow a package into a multi-target layout by adding:
src/lib.rssrc/bin/extra.rsexamples/demo.rstests/basic.rs
This sequence helps learners see that Cargo project creation is not one command to memorize, but a flexible scaffolding system for different project shapes.
Mental Model Summary
The main concepts fit together like this:
cargo newcreates a new package in a new directorycargo initcreates a package in the current directory--binstarts with an executable-oriented layout--libstarts with a reusable-library layout- Cargo chooses a minimal initial scaffold based on convention
- the package can later grow to include more targets
- scaffolding is about giving code a standard, buildable structure from the beginning
A strong working model is:
"Cargo scaffolding creates the smallest standard Rust package that matches the kind of project I want to start, while leaving room for that package to grow."
