Rust Fundamentals: Ownership, Errors and Fearless Concurrency · Rust Basics and Ownership

Hello, Rust: Cargo, Variables and Mutability

Create a project with Cargo, print with println!, and learn Rust's variables: immutable by default, mut when you need change, shadowing to transform a value, and constants.

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

A tiny receipt printer: it stores prices, adds tax, and prints a formatted total. Along the way you'll use Cargo, println! formatting, immutable and mutable variables, shadowing and constants.

The idea

Rust is a compiled language for fast, reliable software: command-line tools, web servers, game engines, embedded devices, and parts of browsers and operating systems. It has no garbage collector, yet prevents whole classes of memory bugs at compile time.

Cargo is Rust's build tool and package manager:

cargo new receipt        # creates Cargo.toml and src/main.rs
cd receipt
cargo run                # compiles and runs
cargo build --release    # optimised binary in target/release/

Key rules for variables:

  • Immutable by default. let total = 10; can't be changed later. This makes code easier to reason about.
  • mut opts in to change: let mut count = 0; count += 1;
  • Shadowing declares a new variable with the same name, often to transform a value (let input = input.trim();); it can even change the type.
  • Constants (const TAX_RATE: f64 = 0.2;) are always immutable, need a type, and are written in SCREAMING_SNAKE_CASE.

println! is a macro (note the !). {} prints a value for users, {:?} prints it for debugging, {:.2} sets decimals, and {name} can use a variable directly.

The code

A receipt with mut, shadowing, constants and formatting
const TAX_RATE: f64 = 0.2;

fn main() {
    let shop = "Corner Cafe";
    let prices = [3.50, 2.25, 4.00];

    let mut subtotal = 0.0;            // mut: we add to it in the loop
    for price in prices {
        subtotal += price;
    }

    let tax = subtotal * TAX_RATE;
    let total = subtotal + tax;

    let total = (total * 100.0).round() / 100.0; // shadowing: a new, rounded `total`

    println!("{shop}");
    println!("items:    {:?}", prices);
    println!("subtotal: {subtotal:>8.2}");
    println!("tax:      {tax:>8.2}");
    println!("total:    {total:>8.2}");

    let items = "3";
    let items: u32 = items.parse().expect("a number"); // shadowing can change the type
    println!("{items} items, average {:.2}", subtotal / items as f64);
}

Output

Corner Cafe
items:    [3.5, 2.25, 4.0]
subtotal:     9.75
tax:          1.95
total:       11.70
3 items, average 3.25

Run this on your computer — see “Get Started” for setup.

Try removing mut from subtotal in your own project: the compiler refuses with cannot assign twice to immutable variable and suggests the fix. Rust's error messages are famously helpful; read them closely.

Common mistakes

  • Adding mut everywhere. Start immutable; add mut only when the compiler (or you) needs change.
  • Confusing shadowing with mutation. Shadowing creates a new variable; the old value is untouched.
  • Mixing integer and float types. subtotal / items fails if one is f64 and the other u32; convert explicitly with as f64.
  • Ignoring Result from parse(). expect("...") crashes on bad input; later lessons handle errors properly.
  • Forgetting the ! on macros like println! and vec!.

Key points

  • Cargo creates, builds and runs projects (cargo new, cargo run, cargo build --release).
  • Variables are immutable unless declared mut; constants are always immutable and typed.
  • Shadowing (let x = ... again) transforms a value, even into another type.
  • println! formats with {}, {:?}, {:.2}, {:>8} and inline {name}.

Further reading: The Rust Programming Language, chapter 3: Common concepts.

Practice questions

What is the difference between `let mut x` and shadowing `let x` again?

Answer

mut makes the same variable changeable (same type). Shadowing declares a new variable with the same name, so it can have a different type and the original binding stays immutable.

What do `{}` and `{:?}` mean in println!?

Answer

{} uses the Display trait (user-facing formatting); {:?} uses the Debug trait (developer-facing formatting, available for most standard types and via #[derive(Debug)] for your own).

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?