Cargo: New, Run, Build & Check

Rust · Lesson 3 of 31 8 min read Updated

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:

Terminal
cargo new marks_report
cd marks_report

Cargo creates a folder called marks_report with this layout:

Text
marks_report/
├── .gitignore
├── Cargo.toml
└── src/
    └── main.rs

It 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).

TOML
[package]
name = "marks_report"
version = "0.1.0"
edition = "2024"

[dependencies]
  • name is the package name; it becomes the name of your program.
  • version follows semantic versioning: major.minor.patch.
  • edition chooses 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:

Rust
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:

Terminal
cargo run
Output
   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 300

The 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:

Terminal
cargo build --release
Output
Compiling 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.

CommandCompilesRunsOutput folderUse it when
cargo checkChecks onlyNononeWriting code, looking for errors
cargo buildYes, dev profileNotarget/debugYou want the program file
cargo runYes, if neededYestarget/debugTrying your program
cargo build --releaseYes, optimisedNotarget/releaseShipping 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.

Rust
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>");
    }
}
Terminal
cargo run -- Rohan
Output
Report 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:

Terminal
cargo add anyhow

Your [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

Terminal
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 folder

Common mistakes

  • Running rustc on files inside a Cargo project. It ignores Cargo.toml and your dependencies. Use cargo run or cargo build.
  • Forgetting the -- before program arguments. cargo run Rohan makes Cargo look for an option called Rohan; write cargo run -- Rohan.
  • Measuring speed with a debug build. Always compare with cargo build --release or cargo run --release.
  • Committing the target folder. It holds large generated files. Cargo's .gitignore already excludes it; keep it that way.

Key points

  • cargo new name creates a package with Cargo.toml, src/main.rs, .gitignore and a Git repository.
  • Cargo.toml holds the package name, version, edition and dependencies; new projects use edition 2024.
  • cargo check only checks, cargo build compiles to target/debug, and cargo run builds and runs.
  • cargo build --release produces an optimised program in target/release.
  • cargo add adds crates from crates.io, and Cargo.lock pins 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
Terminal
cargo new shop_bill
cd shop_bill
Rust
fn main() {
    let prices = [120, 45, 299];
    let total = prices[0] + prices[1] + prices[2];
    println!("Total bill: Rs. {total}");
}
Terminal
cargo run
cargo build --release
Output
Total 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?

2. Where does cargo build --release put the program?

3. What is Cargo.lock for?

4. How do you pass the argument Meera to your program with 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