Every program needs to make decisions and repeat work. Rust's tools for this will look familiar if you've used basically any C-like language — with a couple of genuinely nice twists that take advantage of everything being an expression, which we covered in the last lesson.
if and else
Conditions don't need parentheses around them, but they do need to be a bool — Rust will not treat 0 or an empty string as "falsy" the way some languages do. This is a deliberate choice that kills off an entire category of "wait, why did that branch run?" bugs.
if as an expression
Because if is an expression, you can use it directly on the right-hand side of a let — no separate "declare, then assign in each branch" dance:
One rule to remember: every branch of an if expression has to evaluate to the same type. Rust needs to know what type description is at compile time, regardless of which branch actually runs — it can't wait until your program is live to find out.
Repeating with loop
loop repeats a block forever, until you tell it to stop with break. Here's the twist: break can hand back a value, which makes loop an expression too.
This shows up constantly in real code — for example, retrying an operation until it succeeds, then breaking out with the successful result.
Labeling loops
If you've nested loops and need to break out of the outer one specifically, label them with a leading apostrophe — yes, it looks unusual, and yes, you'll get used to it fast:
while loops
When you want to keep going as long as some condition holds, reach for while — it's the more readable choice over a bare loop plus a manual if/break:
for loops — the one you'll actually use most
for loops over the items in a collection. In practice, this is the loop you'll reach for constantly, because it's both the safest and the most concise — there's no index to manage and no off-by-one mistakes to make.
That 1..=5 is a range — exclusive ranges look like 1..5 (stops before 5), inclusive ones add the equals sign, 1..=5 (includes 5). You'll see both all over real Rust code, especially when slicing collections later in the course.
Coming from a language with C-style for loops? Rust deliberately doesn't have
for (let i = 0; i < n; i++). Manually managing loop counters is exactly the kind of error-prone bookkeeping Rust tries to design away. If you find yourself wanting an index, look at.enumerate()— we'll meet it properly in the iterators lesson.
A first look at match
You'll see match everywhere in Rust — it's like a supercharged switch that the compiler actually checks for completeness. We'll give it a full lesson once we've covered enums, but here's a taste so the syntax isn't a surprise when it shows up sooner:
The big difference from a typical switch: Rust requires every possible value to be handled. Forget a case, and your code won't compile — not "might crash in production three weeks from now," but won't compile, today, on your machine. That other arm at the end is what makes this particular match exhaustive.
Quick exercise
Write a small program that loops from 1 to 20. For each number: print "Fizz" if it's divisible by 3, "Buzz" if divisible by 5, "FizzBuzz" if both, and the number itself otherwise. (Yes, it's the classic. It's a genuinely good way to get comfortable with for, if/else if, and the modulo operator % all at once.)
With branching and looping under your belt, you've got the basic toolkit every program needs. Before we move on to Rust's headline feature — ownership — let's take a quick detour through comments and documentation, since you'll want them from here on out.