Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Rust (2024 edition)  Module 1 – Getting started: rustup, Cargo and compiler messages

Your first Cargo project: build, run and test

Create a package with cargo new, check, build and run it, compare the dev and release profiles, pass arguments, read Cargo.lock and run tests.

  • Beginner
  • 15 minutes
  • Examples run with Rust 1.99.0 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Create, check, build and run a Cargo package
  • Explain the dev and release profiles, the target directory and Cargo.lock
  • Pass arguments to a program through cargo run --
  • Run a package's tests and read cargo test's summary and exit status

Before you start

What you’ll learn · Before you start

What you will learn

  • Create, check, build and run a Cargo package
  • Explain the dev and release profiles, the target directory and Cargo.lock
  • Pass arguments to a program through cargo run --
  • Run a package's tests and read cargo test's summary and exit status

Before you start

On this page

The same 8-line program printed seats booked: 4 when Cargo built it one way, and stopped with “attempt to add with overflow” when Cargo built it another way. Both runs were right: Cargo builds every package: A folder with a Cargo.toml at its top: one or more crates, a program, a library or both, together with the description Cargo needs to build, test and share them. in a profile: A named set of compiler settings that Cargo builds with. dev is the default for cargo build and cargo run; release, chosen with --release, optimises and turns overflow checks off., and the two default profiles check different things.

This lesson starts a package with cargo new and follows it through cargo check, cargo build and cargo run, then explains the dev and release profiles, the target folder and Cargo.lock, how arguments reach your program, and how cargo test reports a failing test, all on recorded runs of Rust 1.99.0.

cargo new writes a package you can run at once

A package is a folder with a Cargo.toml at the top: the code, and a description of how to build it. cargo new hello writes one, and this session lists what it wrote and runs it:

A new package and its first run Python · new_session.py
from session import run

# A new package, the files cargo new wrote, and its first run.
run("""
cargo new hello
find hello -not -path '*/.git/*' -not -name .git | sort
cat hello/Cargo.toml
cat hello/src/main.rs
cat hello/.gitignore
cd hello
cargo run
""")

Output

$ cargo new hello
    Creating binary (application) `hello` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
$ find hello -not -path '*/.git/*' -not -name .git | sort
hello
hello/.gitignore
hello/Cargo.toml
hello/src
hello/src/main.rs
$ cat hello/Cargo.toml
[package]
name = "hello"
version = "0.1.0"
edition = "2024"

[dependencies]
$ cat hello/src/main.rs
fn main() {
    println!("Hello, world!");
}
$ cat hello/.gitignore
/target
$ cd hello
$ cargo run
   Compiling hello v0.1.0 (~/hello)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in …
     Running `target/debug/hello`
Hello, world!

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 new_session.py

Three files appeared, in hello and its src folder, beside a hidden .git folder that the listing leaves out. The manifest: A package's Cargo.toml: its name, version and edition in the [package] table, the other packages it depends on, and settings such as profiles., Cargo.toml, names the package and its version and says which edition it is written in: edition = "2024", the newest, which cargo new chooses by default. Its empty [dependencies] table is where other packages will be listed. src/main.rs is the program, and the name matters: Cargo builds src/main.rs into a program and src/lib.rs into a library, the file cargo new --lib writes instead. The .gitignore holds one line, /target, because cargo new also makes the folder a git repository, unless it is already inside one.

cargo run compiled the package, said which profile it used, and ran the program. The time it reports changes on every run, so these pages show it as …; on your computer it is a number of seconds.

Checkpoint

Check: What cargo new writes

  1. Question 1 of 2 Which file makes Cargo build a package into a program you can run, rather than a library?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: src/main.rs

    Cargo finds a package's targets by their file names: src/main.rs is a program, src/lib.rs a library. A package can have both.

  2. Question 2 of 2 You run cargo new with Rust 1.99.0. Which edition does the new Cargo.toml say?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: edition = "2024"

    The Cargo book gives 2024 as the default edition of cargo new, the newest stable edition of Rust 1.99.0, and the manifest says so explicitly, so the package keeps it with every newer compiler.

cargo check, build and run stop at different steps

Building a program takes several steps, and the three commands stop at different ones. Step through what each command of the next session does:

Interactive · stage by stage Where cargo check, cargo build and cargo run stop Step through the four commands of the session below. Each one runs the stages up to its own last one.
Where cargo check, cargo build and cargo run stop, step 4 of 4. Showing Read Cargo.toml, Check the code, Generate machine code, Link the program (target/debug/hello written), Run it (Hello, world!). In focus: fresh, Run it.nothing changedRead Cargo.tomlname, version, editionCheck the codesyntax, types, borrowsGenerate machine codeopt-level 0 in devLink the programtarget/debug/hello writtenRun itHello, world!

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. cargo check. cargo check reads Cargo.toml and checks the code, the step where most compiler errors are found, then stops. It writes only data for later checks, so target/debug gets no program.
  2. cargo build. cargo build goes on: it generates machine code and links it into target/debug/hello, a program of its own. It prints Compiling instead of Checking.
  3. The program by itself. ./target/debug/hello runs the built program directly. Cargo is not involved at all: the file is an ordinary executable.
  4. cargo run, up to date. cargo run builds first if the sources changed. Here nothing had, so it printed only Finished and ran the program at once.

Here is the same package, new again, checked, built and run, with the contents of target/debug listed after the check and after the build:

check, then build, then run Python · build_session.py
from session import run

# The same new package, checked, then built, then run: watch what appears in target/debug.
run("""
cargo new hello
cd hello
cargo check
ls target/debug
cargo build
ls target/debug
./target/debug/hello
cargo run
""")

Output

$ cargo new hello
    Creating binary (application) `hello` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
$ cd hello
$ cargo check
    Checking hello v0.1.0 (~/hello)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in …
$ ls target/debug
build
deps
examples
incremental
$ cargo build
   Compiling hello v0.1.0 (~/hello)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in …
$ ls target/debug
build
deps
examples
hello
hello.d
incremental
$ ./target/debug/hello
Hello, world!
$ cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in …
     Running `target/debug/hello`
Hello, world!

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 build_session.py

After cargo check, target/debug held only Cargo’s working folders; after cargo build it held hello, a program that ./target/debug/hello ran without Cargo. The Cargo book describes cargo check as compiling “without performing the final step of code generation”, which is why it is faster, and the reason it is the command to run again and again while you write code. The same page adds a caveat: a few errors only appear during code generation, so run cargo build before you are done. cargo run builds whatever has changed and then runs the program; the second time, nothing had changed, and it only printed Finished.

Checkpoint

Check: cargo check, build and run

  1. Question 1 of 2 Right after cargo check in a new package, what does target/debug hold?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Cargo's working folders, but no program yet

    cargo check checks the code and skips code generation, so it is fast and produces no executable. The listing of target/debug before and after cargo build shows the difference: hello appears only after the build.

  2. Question 2 of 2 You change one line of src/main.rs and type cargo run. What does Cargo do?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Compiles the package again, then runs the new program

    When nothing has changed, cargo run prints Finished and runs the program at once, as in the session. When a source file has changed, it compiles first, then runs.

Two profiles: dev for working, release for shipping

Every build uses a profile, a named set of compiler settings. cargo build, cargo run and cargo check use dev, and --release selects release. These are the defaults of the Cargo reference:

Setting dev release
opt-level, how hard to optimise 0 3
debug, information for debuggers yes no
debug-assertions and overflow-checks on off
incremental, reuse earlier work yes no

Timing a program built with cargo run

cargo run builds with dev, at opt-level 0. A program timed that way can be much slower than the one users get, so measure a build made with --release, and say which profile you measured.

The difference in checks is not only about speed. This program books ten more seats in a u8, which holds 0 to 255:

seats/src/main.rs: ten bookings past the limit Rust · seats/src/main.rs
fn main() {
    // A u8 holds 0 to 255; ten more bookings take 250 past the top.
    let mut booked: u8 = 250;
    for _ in 0..10 {
        booked += 1;
    }
    println!("seats booked: {booked}");
}

Its Cargo.toml also defines a profile of its own, release-checked: everything of release, plus the overflow checks of dev. The session runs it in all three profiles:

seats/Cargo.toml: a custom profile that inherits from release TOML · seats/Cargo.toml
[package]
name = "seats"
version = "0.1.0"
edition = "2024"

[dependencies]

# A profile of your own: everything of release, plus the overflow checks of dev.
[profile.release-checked]
inherits = "release"
overflow-checks = true
One program, three profiles Python · profiles_session.py
from session import run

# One program in three profiles, then what the three builds left in the package folder.
run("""
cd seats
!cargo run
cargo run --release
!cargo run --profile release-checked
ls target
cat Cargo.lock
""")

Output

$ cd seats
$ cargo run
   Compiling seats v0.1.0 (~/seats)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in …
     Running `target/debug/seats`

thread 'main' (…) panicked at src/main.rs:5:9:
attempt to add with overflow
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
[exit status 101]
$ cargo run --release
   Compiling seats v0.1.0 (~/seats)
    Finished `release` profile [optimized] target(s) in …
     Running `target/release/seats`
seats booked: 4
$ cargo run --profile release-checked
   Compiling seats v0.1.0 (~/seats)
    Finished `release-checked` profile [optimized] target(s) in …
     Running `target/release-checked/seats`

thread 'main' (…) panicked at src/main.rs:5:9:
attempt to add with overflow
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
[exit status 101]
$ ls target
CACHEDIR.TAG
debug
release
release-checked
$ cat Cargo.lock
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4

[[package]]
name = "seats"
version = "0.1.0"

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 profiles_session.py

In dev the addition panicked, “attempt to add with overflow”, exit status 101. In release it wrapped silently to 4. In release-checked, optimised like release but with overflow-checks = true, it panicked again. Each profile wrote its own folder in target, as the Cargo book’s page on the build cache lists them: debug for dev, release for release, and release-checked, named after the custom profile.

Key idea

A profile is a set of compiler settings: dev for working, with every check on, and release for shipping, optimised and without overflow checks. Code that relies on a check must turn it on in the profile it ships with.

Checkpoint

Check: The dev and release profiles

  1. Question 1 of 2 Which command made the seats program print "seats booked: 4"?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: cargo run --release

    dev and release-checked have overflow checks on and panicked with "attempt to add with overflow". Only release wrapped silently: 250 + 10 is 260, which is 4 past 256.

  2. Question 2 of 2 Where does cargo build --profile release-checked put the program it builds?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: target/release-checked

    The Cargo book: dev builds go into target/debug and release builds into target/release, and a custom profile gets a folder of its own name inside target, so builds in different profiles never overwrite one another.

Cargo.lock records the exact versions, and target holds the builds

The last command of the previous session printed Cargo.lock, which Cargo wrote beside Cargo.toml at the first build. It lists every package in the build with its exact version; seats has no dependencies, so it lists only itself. Once a package has dependencies, Cargo.toml says which versions are acceptable, such as any compatible release of a library, and Cargo.lock records the exact ones that were chosen, so the next build on any computer uses the same. Step through how the package folder grows:

Interactive · step by step What a package folder holds, from cargo new to three builds Step through the package's life. Watch which files cargo new writes and which ones each build adds.
What a package folder holds, from cargo new to three builds, step 5 of 5. Showing hello/, Cargo.toml, src/main.rs, .gitignore (keeps target/ out of git), Cargo.lock (committed with the code), target/debug/, target/release/, target/release-checked/. In focus: .gitignore, Cargo.lock.hello/the package folderCargo.tomlname, version, editionsrc/main.rsfn main.gitignorekeeps target/ out of gitCargo.lockcommitted with the codetarget/debug/the dev buildtarget/release/the release buildtarget/release-checked/a profile of your own

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. cargo new. cargo new hello writes the folder, the manifest Cargo.toml, src/main.rs and a .gitignore, and makes the folder a git repository.
  2. The first build. The first build writes Cargo.lock beside Cargo.toml and puts everything it compiles in target/debug, the folder of the dev profile.
  3. A release build. cargo build --release, or cargo run --release, compiles again with the release profile and keeps its output apart, in target/release.
  4. A profile of your own. A custom profile such as release-checked gets a folder of its own name, target/release-checked, so no build overwrites another.
  5. What git keeps. The .gitignore holds one line, /target, so the builds stay out of version control. Cargo.lock is not ignored: by default cargo new tracks it in version control.

Two rules follow from this. target holds only what can be rebuilt, so the .gitignore of cargo new keeps it out of version control, and you can delete it whenever you need the space back. Cargo.lock is different: by default cargo new tracks it in version control, and the Cargo FAQ says that whether you commit it depends on the needs of your package, with one limit to keep in mind. It gives your own builds the same versions every time, but as the FAQ puts it, “Cargo.lock does not affect the consumers of your package, only Cargo.toml does that”: a library’s users get the versions their own build chooses.

Checkpoint

Check: Cargo.lock and the target folder

  1. Question 1 of 2 What does Cargo.lock record?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The exact version of every package in the build

    Cargo.toml says which versions of a dependency are acceptable; Cargo.lock records the exact ones the build used. For seats, which has no dependencies, it lists only seats itself.

  2. Question 2 of 2 Your disk is full, and target/ of a package is 2 GB. What happens if you delete it?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Nothing is lost; the next build compiles everything again

    Everything in target can be rebuilt, which is also why cargo new's .gitignore keeps it out of version control. The cost of deleting it is only the time of the next full build.

Arguments for your program come after a double dash

A program reads its command-line arguments with std::env::args(), whose first item is the program’s own path. This one greets every name it is given and exits with status 2 when there is none:

namaste/src/main.rs: greets each name it is given Rust · namaste/src/main.rs
use std::env;
use std::process::ExitCode;

fn main() -> ExitCode {
    // The first argument is the program's own path; the names come after it.
    let names: Vec<String> = env::args().skip(1).collect();
    if names.is_empty() {
        eprintln!("usage: namaste NAME...");
        return ExitCode::from(2);
    }
    for name in &names {
        println!("Namaste, {name}!");
    }
    ExitCode::SUCCESS
}

cargo run takes options of its own, so it needs a way to tell them apart from the program’s. The rule of the Cargo book: everything after -- goes to the program, everything before it to Cargo:

Arguments for Cargo and arguments for the program Python · args_session.py
from session import run

# Arguments after -- go to the program; arguments before it go to Cargo.
run("""
cd namaste
cargo run -- Asha Ravi
cargo run -- --release
cargo run --release -- Asha
!cargo run
""")

Output

$ cd namaste
$ cargo run -- Asha Ravi
   Compiling namaste v0.1.0 (~/namaste)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in …
     Running `target/debug/namaste Asha Ravi`
Namaste, Asha!
Namaste, Ravi!
$ cargo run -- --release
    Finished `dev` profile [unoptimized + debuginfo] target(s) in …
     Running `target/debug/namaste --release`
Namaste, --release!
$ cargo run --release -- Asha
   Compiling namaste v0.1.0 (~/namaste)
    Finished `release` profile [optimized] target(s) in …
     Running `target/release/namaste Asha`
Namaste, Asha!
$ cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in …
     Running `target/debug/namaste`
usage: namaste NAME...
[exit status 2]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 args_session.py

cargo run -- --release built in dev and greeted someone called --release, because the program received it. cargo run --release -- Asha gave --release to Cargo and Asha to the program. And with no names, the program’s exit status 2 came straight back through cargo run, which is what a script that runs your program will see.

An option on the wrong side of --

cargo run -- --release passes --release to your program and builds in dev. Options for Cargo, such as --release, go before --; options and arguments for your program go after it.

Checkpoint

Check: Arguments after --

  1. One question Which command builds with the release profile and passes --verbose to your program?

    Choose one answer.

    Show the answer: One question

    Answer: cargo run --release -- --verbose

    The Cargo book: the arguments after -- are passed to the program, the ones before it go to Cargo. The session's cargo run -- --release showed the slip: the program greeted someone called --release.

cargo test runs every test and fails when one fails

Tests are functions marked #[test]. Unit tests sit beside the code, in a module marked #[cfg(test)] that is compiled only for tests; integration test: A test in a file of a package's tests folder. Each file is compiled as a separate program that uses the library from outside, as any other crate would. are files in a tests folder that use the library the way any other A unit of Rust code that the compiler builds as a whole: a library or a program. A Cargo package holds one or more crates. would. This library has one function and three tests, one of which describes a rule the function does not follow yet:

greeting/src/lib.rs: greet and two unit tests Rust · greeting/src/lib.rs
/// A greeting for one person, by name.
pub fn greet(name: &str) -> String {
    format!("Namaste, {name}!")
}

#[cfg(test)]
mod tests {
    use super::greet;

    #[test]
    fn greets_by_name() {
        assert_eq!(greet("Asha"), "Namaste, Asha!");
    }

    #[test]
    fn ignores_spaces_around_the_name() {
        assert_eq!(greet("  Ravi "), "Namaste, Ravi!");
    }
}
greeting/tests/sample.rs: an integration test Rust · greeting/tests/sample.rs
use greeting::greet;

#[test]
fn greets_two_people() {
    assert_eq!(greet("Meera"), "Namaste, Meera!");
    assert_eq!(greet("Kabir"), "Namaste, Kabir!");
}

The session runs every test, with --test-threads=1 after --, so that the tests run one at a time and print in the same order on every run, and then the integration test file sample on its own:

A failing unit test, then one test file Python · test_session.py
from session import run

# All the package's tests, one at a time so the order stays the same, then one integration test file on its own.
run("""
cd greeting
!cargo test -- --test-threads=1
cargo test --test sample
""")

Output

$ cd greeting
$ cargo test -- --test-threads=1
   Compiling greeting v0.1.0 (~/greeting)
    Finished `test` profile [unoptimized + debuginfo] target(s) in …
     Running unittests src/lib.rs (target/debug/deps/greeting-…)

running 2 tests
test tests::greets_by_name ... ok
test tests::ignores_spaces_around_the_name ... FAILED

failures:

---- tests::ignores_spaces_around_the_name stdout ----

thread 'tests::ignores_spaces_around_the_name' (…) panicked at src/lib.rs:17:9:
assertion `left == right` failed
  left: "Namaste,   Ravi !"
 right: "Namaste, Ravi!"
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::ignores_spaces_around_the_name

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in …

error: test failed, to rerun pass `--lib`
[exit status 101]
$ cargo test --test sample
    Finished `test` profile [unoptimized + debuginfo] target(s) in …
     Running tests/sample.rs (target/debug/deps/sample-…)

running 1 test
test greets_two_people ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in …

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 test_session.py

The Finished lines name test, the profile that cargo test builds with. Read the failure from the bottom up. The summary says 1 passed; 1 failed, and the command exited with status 101, which is how a script or CI notices. Above it, the assertion shows both values: greet kept the spaces around Ravi. cargo test stopped after the first test program that failed, so the integration tests never ran in that command; --no-fail-fast runs them all anyway. cargo test --test sample ran just that file, and it passed.

Checkpoint

Check: Running and reading cargo test

  1. Question 1 of 2 cargo test runs a test that fails. With which exit status does the command end?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 101

    cargo test ends with status 101 when a test fails, and with 0 when every test passes. That is how CI turns a red test into a red build without reading the text.

  2. Question 2 of 2 Which command runs only the integration tests in tests/sample.rs?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: cargo test --test sample

    Each file in tests is its own test program, and cargo test --test <name> runs one of them. A plain word after cargo test filters the tests of every program by name instead.

Practise: make the failing test pass

Change greet in your own copy of the package so that ignores_spaces_around_the_name passes, without breaking the other two tests. Then decide what greet(" ") should return, write a test that says so, and make it pass too. When your tests pass, compare with one way to do it:

greeting-solved/src/lib.rs: trim, and greet without a name Rust · greeting-solved/src/lib.rs
/// A greeting for one person, by name. Spaces around the name are ignored,
/// and a name that is only spaces gives a greeting without a name.
pub fn greet(name: &str) -> String {
    let name = name.trim();
    if name.is_empty() {
        return String::from("Namaste!");
    }
    format!("Namaste, {name}!")
}

#[cfg(test)]
mod tests {
    use super::greet;

    #[test]
    fn greets_by_name() {
        assert_eq!(greet("Asha"), "Namaste, Asha!");
    }

    #[test]
    fn ignores_spaces_around_the_name() {
        assert_eq!(greet("  Ravi "), "Namaste, Ravi!");
    }

    #[test]
    fn greets_without_a_name() {
        assert_eq!(greet("   "), "Namaste!");
    }
}
Every test of the solved package Python · solved_session.py
from session import run

# One way to make every test pass: trim the name, and greet without a name when nothing is left.
run("""
cd greeting-solved
cargo test -- --test-threads=1
""")

Output

$ cd greeting-solved
$ cargo test -- --test-threads=1
   Compiling greeting v0.1.0 (~/greeting-solved)
    Finished `test` profile [unoptimized + debuginfo] target(s) in …
     Running unittests src/lib.rs (target/debug/deps/greeting-…)

running 3 tests
test tests::greets_by_name ... ok
test tests::greets_without_a_name ... ok
test tests::ignores_spaces_around_the_name ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in …

     Running tests/sample.rs (target/debug/deps/sample-…)

running 1 test
test greets_two_people ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in …

   Doc-tests greeting

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in …

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 solved_session.py

trim() removes the spaces at both ends, and an empty name after trimming gets a greeting of its own. This time all three test programs ran: the unit tests, the integration test and the documentation tests, of which there are none yet.

TOML ↔ JSON Converter Check a Cargo.toml you have written: see the tables and keys Cargo will read, and the line of any TOML error.

Finally, the questions on packages, profiles, Cargo.lock and tests. Each choice carries its own reason, and the ideas you miss are asked again before the ones you know:

Practice that adapts to you

Practise this lesson

14 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 14 Besides writing Cargo.toml and src/main.rs, what else does cargo new do?

    Choose one answer.

    Show the answer: Question 1 of 14

    Answer: It makes the folder a git repository and writes a .gitignore, unless the folder is already in a repository

    cargo new writes the manifest, src/main.rs and a .gitignore with /target, and initialises a git repository unless --vcs none is given or the folder is already inside one.

  2. Question 2 of 14 Which key of Cargo.toml decides which edition's language rules your code is compiled with?

    Choose one answer.

    Show the answer: Question 2 of 14

    Answer: edition, in the [package] table

    edition = "2024" in [package] is the crate's choice of language rules. It is separate from the compiler release, which rustup picks, and from rust-version, the oldest release you support.

  3. Question 3 of 14 Why is cargo check faster than cargo build?

    Choose one answer.

    Show the answer: Question 3 of 14

    Answer: It skips code generation, the final step of compiling

    Checking syntax, types and borrows finds most errors, and generating and linking machine code is the slow part that cargo check leaves out. That makes it the command to run while you write code.

  4. Question 4 of 14 cargo check reports no errors, yet cargo build fails with a compile error. How can that be?

    Choose one answer.

    Show the answer: Question 4 of 14

    Answer: Some errors are found only during code generation, which cargo check skips

    cargo check is the fast loop, not the whole build. Run cargo build, or the tests, before you call the code done, because the last step of compiling can still report problems.

  5. Question 5 of 14 Which settings differ between the default dev and release profiles? Choose every one.

    Choose every answer that is right.

    Show the answer: Question 5 of 14

    Answer:

    • opt-level
    • debug
    • overflow-checks

    The Cargo reference lists the defaults: dev has opt-level 0, debug information, debug assertions and overflow checks; release has opt-level 3 and none of them. The edition is not a profile setting.

  6. Question 6 of 14 A colleague times a program run with cargo run and finds it much slower than expected. What do you check first?

    Choose one answer.

    Show the answer: Question 6 of 14

    Answer: That it was built with --release, because cargo run uses the dev profile at opt-level 0

    dev builds compile fast and run slowly, with optimisation off and every check on. Compare programs built with --release, and say which profile a number comes from.

  7. Question 7 of 14 A billing service must never wrap money silently, but must still run optimised. What do you add to its Cargo.toml?

    Choose one answer.

    Show the answer: Question 7 of 14

    Answer: overflow-checks = true in [profile.release]

    overflow-checks is a profile setting, so it can be turned on for release, or for a custom profile that inherits release, as release-checked did. Code that handles money should also use checked arithmetic where a value can pass its limit.

  8. Question 8 of 14 Your library's Cargo.lock records a dependency at 1.4.2. A user adds your library to their application. Which version of the dependency does their build use?

    Choose one answer.

    Show the answer: Question 8 of 14

    Answer: The version their own build resolves from your Cargo.toml requirement, not necessarily 1.4.2

    Each build has one Cargo.lock, at its top. Your library's lockfile makes your own builds and CI repeatable, while your users get what their resolution picks within your Cargo.toml requirements.

  9. Question 9 of 14 What does the .gitignore that cargo new writes keep out of version control?

    Choose one answer.

    Show the answer: Question 9 of 14

    Answer: The target folder only

    /target is the only line. Build output stays out of the repository, while Cargo.toml, Cargo.lock and the sources go in.

  10. Question 10 of 14 What is the first item that std::env::args() gives a program?

    Choose one answer.

    Show the answer: Question 10 of 14

    Answer: The program's own path

    args() yields the program's path first and then each argument. namaste calls .skip(1) so that only the names are greeted.

  11. Question 11 of 14 namaste exits with status 2 when it gets no names. With which exit status does cargo run end in that case?

    Type a number.

    Show the answer: Question 11 of 14

    Answer: 2

    The session shows [exit status 2]: cargo run passes the program's own exit status through. A script that calls cargo run, or the program itself, sees the same status.

  12. Question 12 of 14 The failing test printed left: "Namaste, Ravi !" and right: "Namaste, Ravi!". Which one is what greet returned?

    Choose one answer.

    Show the answer: Question 12 of 14

    Answer: left, because the test was assert_eq!(greet(" Ravi "), "Namaste, Ravi!")

    left is the first argument of assert_eq!, here greet(" Ravi "), and right the second, the expected greeting. The spaces in left show that greet kept them, which is the rule the practice asks you to add.

  13. Question 13 of 14 In the session, the unit tests failed and the integration test in tests/sample.rs did not run in that command. Why?

    Choose one answer.

    Show the answer: Question 13 of 14

    Answer: cargo test stops after the first test program that fails; --no-fail-fast runs them all

    The unit tests, each file in tests and the documentation tests are separate test programs. cargo test runs them in turn and stops at the first one that fails, unless --no-fail-fast is given.

  14. Question 14 of 14 Why does the session pass --test-threads=1 after the -- of cargo test?

    Choose one answer.

    Show the answer: Question 14 of 14

    Answer: So that the tests run one at a time and print in the same order on every run

    The arguments after -- go to the test program, and --test-threads=1 tells it to run one test at a time. Tests that share files or other state need the same option, or a fix that removes the sharing.

Interview questions

Warm-up: what is the difference between cargo check, cargo build and cargo run?

All three read Cargo.toml and compile the package, and they stop at different points. cargo check checks the code, syntax, types and borrows, and skips code generation, so it is the fastest way to see compiler errors and produces no program. cargo build goes on to generate machine code and link the program, into target/debug with the dev profile, or target/release with --release. cargo run builds whatever has changed and then runs the program, passing it the arguments after --. A good answer adds two details: a few errors appear only during code generation, so cargo check alone is not a full build, and cargo run uses the dev profile unless you pass --release, which matters for timing and for overflow checks.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A colleague rewrote a log-parsing script in Rust, timed it with cargo run on a 2 GB file, and found it slower than the Python script. What do you check, and in what order, before anyone draws a conclusion?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    Your library's CI is green, with Cargo.lock committed, but a user reports that a new project depending on your library no longer builds: a new release of one of your dependencies broke it. Explain how both can be true, and what you change.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • cargo new writes a package: Cargo.toml with the 2024 edition, src/main.rs (or src/lib.rs with --lib), a .gitignore for /target and a git repository.
  • cargo check stops after checking the code and is the fast loop; cargo build also generates code and links a program into target/debug; cargo run builds what changed and runs it.
  • The dev profile keeps overflow checks and debug information; release optimises and wraps silently on overflow, and a custom profile with inherits can combine the two.
  • Cargo.lock records the exact version of every package in the build and is tracked in version control by default, while target holds only rebuildable output.
  • Arguments after -- go to your program and options before it to Cargo, and cargo test exits with 101 when a test fails, after showing both values of the failed assertion.

Terms in this lesson

integration test
A test in a file of a package's tests folder. Each file is compiled as a separate program that uses the library from outside, as any other crate would.
manifest
A package's Cargo.toml: its name, version and edition in the [package] table, the other packages it depends on, and settings such as profiles.
package
A folder with a Cargo.toml at its top: one or more crates, a program, a library or both, together with the description Cargo needs to build, test and share them.
profile
A named set of compiler settings that Cargo builds with. dev is the default for cargo build and cargo run; release, chosen with --release, optimises and turns overflow checks off.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.