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.
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. mutopts 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 inSCREAMING_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
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
muteverywhere. Start immutable; addmutonly 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 / itemsfails if one isf64and the otheru32; convert explicitly withas f64. - Ignoring
Resultfromparse().expect("...")crashes on bad input; later lessons handle errors properly. - Forgetting the
!on macros likeprintln!andvec!.
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.