diff --git a/examples/03_conditionals_and_loops/1_intro.md b/examples/03_conditionals_and_loops/1_intro.md index bc7d639..b4ce1f1 100644 --- a/examples/03_conditionals_and_loops/1_intro.md +++ b/examples/03_conditionals_and_loops/1_intro.md @@ -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` @@ -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. @@ -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 @@ -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` @@ -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; @@ -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. diff --git a/examples/03_conditionals_and_loops/3_ferris_mood.md b/examples/03_conditionals_and_loops/3_ferris_mood.md index cc2aa1d..04b14d9 100644 --- a/examples/03_conditionals_and_loops/3_ferris_mood.md +++ b/examples/03_conditionals_and_loops/3_ferris_mood.md @@ -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). diff --git a/examples/03_conditionals_and_loops/4_factorial.md b/examples/03_conditionals_and_loops/4_factorial.md index 3d80090..bc989fb 100644 --- a/examples/03_conditionals_and_loops/4_factorial.md +++ b/examples/03_conditionals_and_loops/4_factorial.md @@ -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. diff --git a/examples/03_conditionals_and_loops/5_count_evens.md b/examples/03_conditionals_and_loops/5_count_evens.md index 9b4b82b..4d8b8a9 100644 --- a/examples/03_conditionals_and_loops/5_count_evens.md +++ b/examples/03_conditionals_and_loops/5_count_evens.md @@ -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`. diff --git a/examples/03_conditionals_and_loops/7_what_we_learned.md b/examples/03_conditionals_and_loops/7_what_we_learned.md index d0cecbe..ee0cf30 100644 --- a/examples/03_conditionals_and_loops/7_what_we_learned.md +++ b/examples/03_conditionals_and_loops/7_what_we_learned.md @@ -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.