Cargo: New, Run, Build & Check
Almost every Rust project, from a tiny exercise to a large web service, is built with Cargo. Cargo creates new projects, compiles your code and its libraries, runs your program and tests, and downloads packages from the internet.
In this lesson you will create a project with cargo new, understand the files it makes, and learn the difference between cargo run, cargo build, cargo check and a release build. You will also add your first dependency.
Create a project with cargo new
Open a terminal in the folder where you keep your code and run:
cargo new marks_report
cd marks_reportCargo creates a folder called marks_report with this layout:
marks_report/
├── .gitignore
├── Cargo.toml
└── src/
└── main.rsIt also starts a new Git repository in the folder (if Git is installed and you are not already inside a repository), and the .gitignore file tells Git to skip the target folder where compiled files go.
A package is a project with a Cargo.toml file. A crate is one compilation unit: a program (a binary crate, starting at src/main.rs) or a library (a library crate, starting at src/lib.rs). cargo new makes a binary by default; cargo new --lib name makes a library.
The Cargo.toml manifest
Cargo.toml is the manifest: it describes your package in the TOML format (simple key = "value" lines grouped under [section] headings).
[package]
name = "marks_report"
version = "0.1.0"
edition = "2024"
[dependencies]nameis the package name; it becomes the name of your program.versionfollows semantic versioning: major.minor.patch.editionchooses the language edition; new projects get"2024".[dependencies]lists the crates (libraries) your code uses. It is empty for now.
src/main.rs already contains a "Hello, world!" program. Replace it with this:
fn main() {
let name = "Priya";
let marks = [78, 91, 85];
let total = marks[0] + marks[1] + marks[2];
println!("{name} scored {total} out of 300");
}cargo run
cargo run compiles the project (only if something changed) and then runs it:
cargo run Compiling marks_report v0.1.0 (/home/priya/marks_report)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
Running `target/debug/marks_report`
Priya scored 254 out of 300The first three lines come from Cargo; the last line is your program. The time and the folder path will be different on your machine, and on Windows the path uses backslashes and the program ends in .exe. Run the command again without editing anything and the Compiling line disappears, because Cargo sees that nothing changed.
cargo build and cargo check
cargo build compiles without running. The program is saved as target/debug/marks_report (marks_report.exe on Windows). This dev profile compiles quickly and keeps debugging information, but the program is not optimised.
cargo check runs all of the compiler's checks (types, ownership, borrowing) but skips producing a program. It is much faster than a build, so many developers run cargo check again and again while writing code and only build when they want to run something. Editors with rust-analyzer run a check for you every time you save.
When you are ready to give the program to users, or to measure its speed, make a release build:
cargo build --releaseCompiling marks_report v0.1.0 (/home/priya/marks_report) Finished `release` profile [optimized] target(s) in 0.74s
The optimised program is in target/release/. Release builds take longer to compile but often run many times faster. Never judge Rust's speed using a debug build.
| Command | Compiles | Runs | Output folder | Use it when |
|---|---|---|---|---|
cargo check | Checks only | No | none | Writing code, looking for errors |
cargo build | Yes, dev profile | No | target/debug | You want the program file |
cargo run | Yes, if needed | Yes | target/debug | Trying your program |
cargo build --release | Yes, optimised | No | target/release | Shipping or benchmarking |
Passing arguments to your program
Arguments after -- go to your program instead of to Cargo. The standard library function std::env::args gives you the arguments as an iterator of String values; the first one is always the program's own path.
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() > 1 {
println!("Report requested for {}", args[1]);
} else {
println!("Usage: marks_report <student name>");
}
}cargo run -- RohanReport requested for Rohan
(Cargo's own Compiling, Finished and Running lines are left out here.) For real command-line tools you will use the clap crate, covered in Popular Crates: serde, clap & anyhow.
Adding a dependency
Rust libraries are called crates, and the public registry for them is crates.io. The cargo add command adds the newest compatible version to your manifest:
cargo add anyhowYour [dependencies] section now contains a line such as anyhow = "1.0.104". The version string means "1.0.104 or any newer 1.x version", because Cargo treats versions as compatible when the major number is the same. The next cargo build downloads and compiles the crate.
Cargo also writes a file called Cargo.lock that records the exact version of every crate used in the build. Commit Cargo.lock to Git so that your teammates and your build server compile exactly the same code. cargo update moves the lock file to newer compatible versions when you choose to.
Other Cargo commands you will use
cargo fmt # format all code in the standard style
cargo clippy # extra lint checks with helpful suggestions
cargo test # build and run tests
cargo doc --open # build HTML docs for your crate and its dependencies
cargo clean # delete the target folderCommon mistakes
- Running
rustcon files inside a Cargo project. It ignoresCargo.tomland your dependencies. Usecargo runorcargo build. - Forgetting the
--before program arguments.cargo run Rohanmakes Cargo look for an option called Rohan; writecargo run -- Rohan. - Measuring speed with a debug build. Always compare with
cargo build --releaseorcargo run --release. - Committing the
targetfolder. It holds large generated files. Cargo's.gitignorealready excludes it; keep it that way.
Key points
cargo new namecreates a package withCargo.toml,src/main.rs,.gitignoreand a Git repository.Cargo.tomlholds the package name, version, edition and dependencies; new projects use edition 2024.cargo checkonly checks,cargo buildcompiles totarget/debug, andcargo runbuilds and runs.cargo build --releaseproduces an optimised program intarget/release.cargo addadds crates from crates.io, andCargo.lockpins exact versions for repeatable builds.
Exercise
Create a new Cargo project called shop_bill. Make main store three item prices in rupees (for example 120, 45 and 299), add them up and print the total. Run it with cargo run, then make a release build and find the optimised program file.
Show solution
cargo new shop_bill
cd shop_billfn main() {
let prices = [120, 45, 299];
let total = prices[0] + prices[1] + prices[2];
println!("Total bill: Rs. {total}");
}cargo run
cargo build --releaseTotal bill: Rs. 464
After cargo build --release the optimised program is target/release/shop_bill (shop_bill.exe on Windows). The cargo run line above shows only the program's output; Cargo's status lines appear before it.
Quick Quiz
1. Which command checks your code for errors fastest, without producing a program?
cargo check runs the compiler's checks but skips code generation, so it is the quickest feedback loop.
2. Where does cargo build --release put the program?
Release builds use the optimised release profile and go to target/release.
3. What is Cargo.lock for?
Cargo.lock pins exact crate versions; commit it so everyone builds the same thing.
4. How do you pass the argument Meera to your program with Cargo?
Everything after -- is passed to your program instead of being read by Cargo.
Learn Rust with a mentor
Free lessons get you started. Our Programming & Software Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
