Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions examples/03_conditionals_and_loops/1_intro.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# Conditionals and loops

*Why did the Rust loop break up with the condition? It said "I just need some space."*

You've already seen `if` and `for` in passing.
This chapter slows down and looks at them on purpose, plus the other two loop forms (`while` and `loop`) and the keywords that control them (`break` and `continue`).
Now we'll slow down and look at them on purpose, along with the other two loop forms (`while` and `loop`) and the keywords that control them (`break` and `continue`).

## `if` / `else` / `else if`

Expand All @@ -19,7 +17,7 @@ if x > 0 {
}
```

Two things to call out:
You might notice two things:

- The condition is a `bool`.
No truthy strings, no zero-as-false, no parentheses required around the condition.
Expand All @@ -34,7 +32,9 @@ Two things to call out:

## `for` loops

`for` walks anything that produces an iterator. Here's how it works:
A `for` loop can walk through a range of numbers, the elements of an array, or the items in a collection.
Rust supports all of these through iterators, but you do not need to understand iterators yet to use the loop.
For example:

```rust
for i in 0..5 { // 0, 1, 2, 3, 4
Expand All @@ -48,10 +48,11 @@ for word in ["hi", "rust"] {

`0..5` is a *range*: a value that produces the integers from `0` up to (but not including) `5`.
The inclusive form is `0..=5`, which also yields `5`.
Both work as iterators and as patterns in `match` (seen in the password chapter later).
Both work as iterators and as patterns in `match` (we'll use ranges that way later in the password chapter).

For larger collections, you'll usually iterate over a `Vec`, a slice, a `HashMap`, or the result of `s.chars()`.
Iterators get their own chapter; for now, "anything you can put on the right of `for x in ...`" is enough.
We'll take a closer look at iterators later.
For now, "anything you can put on the right of `for x in ...`" is enough.

## `while` and `loop`

Expand All @@ -66,7 +67,7 @@ while n > 0 {
```

`loop` runs forever, until you `break` out of it.
Useful when the exit condition isn't a simple boolean check at the top:
It is useful when the exit condition is not a simple boolean check at the top, or when you only know whether to stop after doing some work:

```rust
let mut attempts = 0;
Expand Down Expand Up @@ -103,11 +104,11 @@ for n in 0..10 {

## Picking the right loop

A useful rule of thumb:
When you need to pick one:

- Use `for` when you know what you're iterating over (a range, a slice, a map, the chars of a string).
- Use `while` when the exit condition is a simple "keep going while X is true".
- Use `loop` only when neither of the above fits, usually because the exit condition is in the middle of the body.

Most code reaches for `for`.
Iterators (covered later in the course) make `for` even more powerful.
If you are unsure, start with `for` when there is already a collection or range to walk through.
When we get to iterators, we'll see how ranges, slices, and collections all plug into this same syntax.
5 changes: 2 additions & 3 deletions examples/03_conditionals_and_loops/3_ferris_mood.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ Implement `ferris_mood(hunger, naps)` returning a `&'static str`, following thes
String literals like `"Hangry"` are baked into your compiled binary, so the text is around for as long as the program is running.
The `'static` *lifetime* is just the compiler's way of saying "this reference will never dangle."
If you've written C, it's the same intuition as a `const char *` pointing at a string literal.
Lifetimes get a proper introduction in the memory and ownership chapter; for now the only thing to take away is *"string literals are always safe to return as `&'static str`."*

## Two things to watch
We'll spend more time with lifetimes in the memory and ownership chapter.
For now, the only thing to take away is *"string literals are always safe to return as `&'static str`."*

**Combining conditions.** The `"Grumpy"` rule needs *both* parts to be true.
Rust spells this `&&` (logical AND).
Expand Down
11 changes: 8 additions & 3 deletions examples/03_conditionals_and_loops/4_factorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

`n!` is `1 * 2 * 3 * ... * n`.
By convention, `0! == 1`.
Build it up with a running accumulator and a `for` loop over an inclusive range.
Build it up with a running accumulator and a `for` loop over the inclusive range `1..=n`.
Start the accumulator at `1`, then multiply each number into it as the loop goes along.

The accumulator pattern shows up everywhere once you start writing loops: `let mut acc = ...; for x in ... { acc = ... }; acc`.
Note the `mut`: bindings are immutable by default, and the loop body needs to update `acc`, so you have to opt in.
There is a nice side effect at the boundary.
When `n` is `0`, the range `1..=n` is empty, so the loop does not run and the initial `1` comes back unchanged.
That gives you `0! == 1` without a special case.

The accumulator pattern shows up everywhere once you start writing loops: begin with one value, update it for every item, then return what you ended up with.
The binding needs `mut` because Rust bindings are immutable unless you explicitly make them mutable.
7 changes: 4 additions & 3 deletions examples/03_conditionals_and_loops/5_count_evens.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Counting evens with `for` and `continue`

The parameter here is a `&[i32]`, a *slice*: a borrowed view over a sequence of `i32` values that live somewhere else.
Slices, and the `&` that borrows them, get a proper treatment in the borrowing and vectors chapters.
For now the only thing you need is that a `for` loop walks a slice one element at a time, handing you each number in turn.
We'll spend more time with slices, and the `&` that borrows them, in the borrowing and vectors chapters.
For now, the only thing you need is that a `for` loop walks a slice one element at a time, handing you each number in turn.

You want to count how many of those numbers are even.
A `for` loop over the slice with a counter you bump on every match does the job.

This is a good place to use `continue`: skip the odds early and the "do work" branch ends up uncluttered.
When the current number is odd, you can use `continue` to skip straight to the next one.
You can then increment the counter without nesting that line inside another `if`.
18 changes: 10 additions & 8 deletions examples/03_conditionals_and_loops/7_what_we_learned.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
# Wrapping up conditionals and loops

You wrote a three-way classifier with `if`/`else if`/`else`, two accumulator-style loops (a `for` over a range and a `for` over a slice with `continue`), and a `while` loop where the iteration count isn't known up front.
You used each kind of control flow for a slightly different job.
The mood classifier chose one branch, the two `for` loops walked through values you already had, and the `while` loop kept going until there was nothing left to divide.

## What we learned

- `if`/`else` is an *expression*, not just a statement.
It can sit on the right of `let`, be returned from a function, or appear anywhere a value is expected.
Both branches must have the same type.
- Conditions are bare `bool` expressions.
No parentheses required, no implicit conversion from integers or strings.
- `for x in iter` is the default loop.
Ranges (`0..n`, `0..=n`), slices, vectors, and most other collections all produce iterators you can put on the right.
- Conditions are `bool` expressions written without surrounding parentheses.
Rust does not quietly treat an integer or a string as true or false.
- `for x in iter` is the type of loop to use when you already have something to walk through.
Ranges (`0..n`, `0..=n`), slices, vectors, and most other collections all work here.
- `while cond` runs as long as the condition is true.
Reach for it when the iteration count depends on values computed inside the loop (like "divide until zero").
- `loop` runs forever until you `break`.
It can also produce a value: `let x = loop { ...; break value; };`.
- `break` exits the innermost loop; `continue` skips to the next iteration.
A `continue` to early-out the boring case usually reads better than nesting the work inside an `if`.
- The accumulator pattern (`let mut acc = ...; for ... { acc = ...; }`) is the how you can "compute one value from many".
Once you meet iterators in the iterators chapter, methods like `sum`, `count`, and `fold` will replace many of these by-hand loops.
In `count_evens`, `continue` handled the odd numbers first and kept the counter outside a nested `if`.
- An accumulator lets you compute one value from many inputs.
You start with a value, update it once per loop iteration, and return it when the loop is done.
When we get to iterators, we'll use methods such as `sum`, `count`, and `fold` for many of the same jobs.
Loading