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
2 changes: 1 addition & 1 deletion examples/01_strings_and_chars/2_welcome.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Your first function: a welcome message
# A welcome message

Time to put `&str` and `String` together.
Implement `format_welcome_message` so it returns the string `"Welcome, {name}!"`.
Expand Down
8 changes: 6 additions & 2 deletions examples/03_conditionals_and_loops/5_count_evens.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Counting evens with `for` and `continue`

Now you have a slice of integers and want to know how many of them are even.
The natural approach is a `for` loop over the slice with a counter that you bump on every match.
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.

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.
4 changes: 2 additions & 2 deletions examples/04_functions/4_sum_to.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Sum to N

Now do recursion the other way around.
Instead of printing as you go, *return* a value built up from smaller answers.
This exercise is about recursion: a function that calls itself.
Each call *returns* a value built up from the answer to a smaller version of the same problem.

Write `sum_to(n)` so it returns `1 + 2 + ... + n`, with `sum_to(0) == 0`.

Expand Down
5 changes: 2 additions & 3 deletions examples/04_functions/4_sum_to.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
//! Exercise: write a function `sum_to` that returns the sum
//! `1 + 2 + ... + n`. By convention, `sum_to(0)` is `0`.
//!
//! Solve it recursively. The pattern is the same as `countdown`,
//! but this time each call returns a value that the caller adds to
//! its own work. Don't use a loop, and don't use the closed-form
//! Solve it recursively: each call returns a value that the caller
//! adds to its own work. Don't use a loop, and don't use the closed-form
//! `n * (n + 1) / 2`; the point is the recursion.

#[test]
Expand Down
4 changes: 2 additions & 2 deletions examples/10_tuples_and_destructuring/1_intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ fn min_max(values: &[i32]) -> (i32, i32) {
let (lo, hi) = min_max(&[3, 1, 4, 1, 5, 9]);
```

That `min_max` body has three pieces of syntax we haven't formally introduced yet.
That `min_max` body has three pieces of syntax worth a quick note.
Don't let them trip you up here:

- `values.iter()` walks the slice one element at a time.
Iterators get a full chapter later; for now read it as "give me each element in turn."
- `.min()` / `.max()` return an `Option` (they'd return `None` for an empty slice).
`.unwrap()` says "I'm sure it's `Some`, give me the value or panic."
`Option` is the next chapter.
- The leading `*` *dereferences* the `&i32` that the iterator hands back, so we end up with an owned `i32` instead of a reference.
- The leading `*` *dereferences* the `&i32` the iterator hands back (the same dereference you met in the hashmaps chapter), so we end up with an owned `i32` instead of a reference.

When you only care about some fields, use `_` to ignore the rest:

Expand Down
2 changes: 1 addition & 1 deletion examples/14_structs_and_methods/6_what_we_learned.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ You defined a struct, wrote a `new` constructor, added a `&self` method that for
- An `impl` block attaches functions to the type.
Without `self`, it's an associated function (called as `User::new(..)`); with `self`, it's a method (called as `user.method()`).
- The three flavors of `self` say what the method intends to do: `&self` reads, `&mut self` mutates in place, plain `self` consumes.
The same ownership rules from the ownership chapter apply.
The same ownership rules from the moves and borrowing chapters apply.
- `Self` (capital S) inside an `impl` block is shorthand for the type.
Returning `Self` keeps the constructor signature stable if the type is later renamed.
- `format!` is the idiomatic way to build a `String` from a template; same syntax as `println!` but returns the string.
Expand Down
2 changes: 1 addition & 1 deletion examples/19_password_validator/4_generate.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Implement `generate_secure_password(length)` that returns a `String` of the requested length, mixing uppercase letters, lowercase letters, digits, and special characters so it would pass a strict validator.

For variability without pulling in `rand`, you can use `std::time::SystemTime::now().duration_since(UNIX_EPOCH)?.subsec_nanos()` as a seed and cycle through your character sets.
For variability without pulling in `rand`, you can use `std::time::SystemTime::now().duration_since(UNIX_EPOCH).unwrap().subsec_nanos()` as a seed and cycle through your character sets.
This is **not** cryptographically secure, but it's plenty for this exercise.
In real code, use the `rand` crate.

Expand Down
2 changes: 1 addition & 1 deletion examples/19_password_validator/5_validate.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Map the final score to `PasswordStrength`:
- `>= 70` → `Strong`

Push a short message into `feedback` for every rule that *fails*.
That way `PasswordAdvisor` (or your own future code) has something to react to.
That way your own follow-up code has something to react to.
The length-related complaint should mention "characters", "length", "short", "longer", or "at least" so the test below can recognise it.

## Useful from the standard library
Expand Down
4 changes: 2 additions & 2 deletions examples/23_smart_pointers/6_what_we_learned.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ You boxed an integer and added it back out, defined a recursive expression-tree
The compiler can lay it out, and recursion mirrors the data exactly.
The same concept underpins parsers, interpreters, and ASTs everywhere.
- `Box<dyn Trait>` is the owned form of a trait object.
It lets you store mixed concrete types behind a single interface (a `Vec<Box<dyn Command>>` of pipeline stages, all different structs, driven through one trait) and underpins the `Box<dyn Error>` pattern you'll meet in the `?` operator chapter.
It lets you store mixed concrete types behind a single interface (a `Vec<Box<dyn Command>>` of pipeline stages, all different structs, driven through one trait) and underpins the `Box<dyn Error>` pattern from the env-file parser chapter.
- Dynamic dispatch through a trait object costs one vtable lookup per call.
That's usually fine.
Reach for generics (`fn f<T: Command>`) when you want the compiler to monomorphize away the indirection.
Expand All @@ -34,4 +34,4 @@ You boxed an integer and added it back out, defined a recursive expression-tree
## Where this goes next

The iterators chapter puts iterators front and center, and you'll see how a chain of `.iter().fold(...)` could have replaced the explicit loop in `apply_pipeline`.
The `?` operator chapter brings `Box<dyn Error>` and the `?` operator together, which is the day-to-day payoff for understanding `Box<dyn Trait>` here.
The env-file parser chapter brings `Box<dyn Error>` and the `?` operator together, which is the day-to-day payoff for understanding `Box<dyn Trait>` here.
10 changes: 5 additions & 5 deletions examples/24_rust_fundamentals_quiz/quiz.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ answers = [
{ text = "`&mut value`", correct = false, explanation = "That's a *mutable* borrow." },
{ text = "`&value`", correct = true, explanation = "The `&` operator produces a shared, immutable reference." },
{ text = "`*value`", correct = false, explanation = "`*` *dereferences* a reference; it doesn't create one." },
{ text = "`value.borrow()`", correct = false, explanation = "`.borrow()` is a `RefCell` method, not the general way to take a reference." },
{ text = "`value.clone()`", correct = false, explanation = "`.clone()` makes an independent owned copy, not a reference to the original." },
]

[[questions]]
prompt = "Which statement about Rust's ownership is correct?"
answers = [
{ text = "A value can have multiple owners at the same time.", correct = false, explanation = "Single ownership is the whole point; at most one owner at any moment (use `Rc`/`Arc` only for shared *handles*)." },
{ text = "A value can have multiple owners at the same time.", correct = false, explanation = "Single ownership is the whole point: at most one owner at any moment." },
{ text = "When an owner goes out of scope, the value is dropped.", correct = true, explanation = "Right. This is how Rust frees memory deterministically, without a garbage collector." },
{ text = "Ownership can be shared via `SharedPtr`.", correct = false, explanation = "`SharedPtr` is a C++ thing. Rust uses `Rc<T>` (single-threaded) or `Arc<T>` (across threads)." },
{ text = "Assigning a value to a second variable always copies it.", correct = false, explanation = "For non-`Copy` types, assignment *moves* ownership; the original binding can't be used afterwards." },
{ text = "Primitive types need manual memory management.", correct = false, explanation = "Primitives implement `Copy`, so they're duplicated on assignment, no manual management." },
]

Expand Down Expand Up @@ -131,8 +131,8 @@ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str
hint = "`'a` is the *shorter* of `x`'s and `y`'s lifetimes."
answers = [
{ text = "Both inputs must outlive the function call.", correct = false, explanation = "That's true of every reference parameter; it's not what `'a` adds." },
{ text = "The return value lives strictly *less* than the shorter input.", correct = false, explanation = "Close, but `'a` says \"at *least* as long as\", not \"strictly less than\"." },
{ text = "The return value lives at least as long as the shorter of the two inputs.", correct = true, explanation = "Right. `'a` is the intersection of the two input lifetimes, and the return reference is valid for that whole window." },
{ text = "The return value lives strictly *less* than the shorter input.", correct = false, explanation = "Not quite: the returned reference can be valid for that whole window, so the bound is \"no longer than\" (it may be equal), not \"strictly less than\"." },
{ text = "The return value can't outlive the shorter of the two inputs.", correct = true, explanation = "Right. `'a` is the intersection of the two input lifetimes, so the returned reference is tied to whichever input is dropped first and can't be used past it." },
{ text = "The two inputs must have identical lifetimes.", correct = false, explanation = "They don't; the compiler picks `'a` as the shorter of the two and unifies via subtyping." },
]

Expand Down
Loading