Skip to content
Closed
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
27 changes: 13 additions & 14 deletions examples/00_integers/1_intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

*This chapter was supposed to start with an integer joke. Turns out, it's pointless.*

You already know how numbers work, so let's start with a quick tour of the types
and then spend the rest of the chapter on what Rust does differently.
You already know how numbers work, so we'll take a quick tour of the types and spend our time on what Rust does differently.

```rust
let byte: u8 = 255; // a single byte, holds 0 to 255
Expand All @@ -14,10 +13,10 @@ let i: usize = 0; // the type for sizes and indices
let price: f64 = 19.99; // floating point (f32 is the smaller one)
```

That covers most of what you'll see in the wild.
Now on to the interesting bits!
## No silent overflows
Those are the types you'll see most often.
The first difference matters when a value no longer fits its type.

## No silent overflows

If you push a number past its type's maximum, most languages won't tell you.
For example, C wraps around, Java wraps around, and Python quietly grows the integer to fit.
Expand All @@ -36,14 +35,15 @@ let total = hp + bonus;
If that bug had been in a C program, the total would be 44 instead of 300.
The player would have no idea why their health bar suddenly *dropped* from 200 to 44 after picking up a bonus item.

Rust catches it at the source instead and tells you.
It's best to be explicit about how you want to handle overflow, and Rust gives you three options:
Rust catches the overflow where it happens instead.
When a result may not fit, choose the behavior that matches the problem:

- `a.saturating_add(b)` clamps at the maximum, so 255 stays 255.
- `a.checked_add(b)` returns `None` on overflow, so you can handle it yourself.
- `a.wrapping_add(b)` opts back into wraparound, for the times you actually want it.

(Release builds wrap by default for speed. Reach for these methods to get the same behavior in both debug and release builds.)
Release builds wrap by default for speed.
Reach for these methods when you need the same behavior in both debug and release builds.

## No implicit conversions

Expand All @@ -61,13 +61,12 @@ let total = price * count as f64;

## Text into numbers

Parsing a string can fail, because the input might not be a number at all. So
`parse` hands back a `Result`.
Parsing a string can fail because the input might not be a number at all, so `parse` hands back a `Result`.

```rust
let n: u32 = "123".parse().unwrap_or(0);
```

We haven't talked about `Result` yet, but the idea is that Rust forces you to deal with the possibility of failure instead of ignoring it.
For now, `.unwrap_or(0)` is fine.
There's a dedicated chapter on `Result` later.
We haven't talked about `Result` yet, but the important part for now is that a failed parse cannot be ignored by accident.
Using `.unwrap_or(0)` is enough for this exercise.
We'll come back to failed parses after introducing `Result`.
1 change: 0 additions & 1 deletion examples/00_integers/2_hints.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,3 @@
You'll need a type annotation so it knows which one.
2. `.parse::<u32>()` returns a `Result<u32, _>`.
The exercise asks for `0` on failure, so reach for `.unwrap_or(0)`.
3. (Forward reference: in the `Result` chapter you'll learn why returning `Result` directly is the better signature.)
22 changes: 9 additions & 13 deletions examples/00_integers/4_damage_with_bonus.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,17 @@ Rust never converts between numeric types implicitly.
If you want to multiply a `u32` by an `f64`, one of them has to change shape first, and you have to say so.
The `as` keyword does the conversion.

The function takes a base damage as `u32` and a bonus as an `f64` percentage (so `50.0` means "+50%"; think a critical hit, an equipment buff, or any other damage modifier), and returns the final damage as a `u32`.
You'll need to:
The function takes base damage as a `u32` and an `f64` bonus percentage, then returns the final damage as a `u32`.
A bonus of `50.0` means adding half of the base damage again, whether it came from a critical hit, equipment, or some other modifier.
Keep the calculation in `f64` so the fractional percentage is not lost, then convert the final damage back to `u32`.

1. Turn the percentage into a multiplier (divide by `100.0`).
2. Multiply it by the base, but only after casting the base to `f64`, because Rust won't mix the two for you.
3. Add the bonus to the base.
4. Cast back to `u32` to return.

That last cast *truncates* the fractional part toward zero, which is exactly what we want here: most games quantise to whole HP, so `8.085` damage becomes `8`, not `9`.
The final test pins this down.
Converting the final value back to `u32` *truncates* the fractional part toward zero.
That matches games which use whole HP, so `8.085` damage becomes `8`, not `9`.
The final test checks that we truncate rather than round.

## Useful from the standard library

- [`as`](https://doc.rust-lang.org/std/keyword.as.html) is the cast operator.
`1.7_f64 as u32` is `1` (truncation), not `2`.
That same truncation is what drops fractional HP in this exercise.
- [`f64::round`](https://doc.rust-lang.org/std/primitive.f64.html#method.round) exists too, and rounds to the nearest integer.
It's the right tool when you want nearest-integer rounding, but this exercise asks for truncation, so you won't need it here.
`1.7_f64 as u32` is `1`, not `2`, because the cast truncates.
- [`f64::round`](https://doc.rust-lang.org/std/primitive.f64.html#method.round) rounds to the nearest integer instead.
This exercise asks for truncation, so compare the two behaviors before choosing.
14 changes: 7 additions & 7 deletions examples/00_integers/5_parse_positive_integer.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# Parsing strings into numbers

`str::parse` is the universal "turn this text into a value of some type" method.
It returns a `Result`, because the input might not be parseable.
We don't have `Result` yet (it gets its own chapter later), so we'll collapse failure to `0` for now.
`str::parse` turns text into the type you ask for.
It returns a `Result` because not every input can become the type you asked for.
We haven't met `Result` yet, so this exercise deliberately maps a failed parse to `0`.

Returning `0` on failure is a *bad idea* in real code: it silently merges "the input was the number zero" with "the input was garbage".
Rust has a much better tool for this in `Option` and `Result`, each of which gets its own chapter.
For now, `parse().unwrap_or(0)` is the shortest way to satisfy the tests.
Returning `0` on failure is a *bad idea* in real code because it makes valid input `"0"` indistinguishable from garbage.
When we get to `Option` and `Result`, we'll preserve that difference.
For now, the tests define `0` as the fallback.

Note that `u32` can't be negative, so `"-5".parse::<u32>()` will fail and we should also return `0`.
If you reach for `i32` first, the test for `"-5"` may surprise you.

## Useful from the standard library

- [`str::parse`](https://doc.rust-lang.org/std/primitive.str.html#method.parse) turns a string into any type that implements `FromStr`.
- [`str::parse`](https://doc.rust-lang.org/std/primitive.str.html#method.parse) turns a string into a type you choose.
Returns a `Result` because the input might not be valid.
- [`Result::unwrap_or`](https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_or) hands back the value on `Ok`, or the fallback you give it on `Err`.
Useful for the "just give me a number" path here.
4 changes: 2 additions & 2 deletions examples/00_integers/6_what_we_learned.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ You met Rust's stance on numbers: overflow is caught rather than ignored, type c
Use `as` for a truncating cast, or `.into()` / `.try_into()` when you want a checked conversion.
- `as u32` on a float truncates toward zero (`1.7 as u32` is `1`); `f64::round` rounds to the nearest integer.
We used truncation for the damage bonus because games drop fractional HP.
- `str::parse()` is the universal text-to-value method.
It returns a `Result`; pair it with `.unwrap_or(...)` until you've met `Result` properly in its own chapter.
- `str::parse()` turns text into a value of a requested type.
It returns a `Result`; for now, pair it with `.unwrap_or(...)` when you need a fallback.
31 changes: 16 additions & 15 deletions examples/01_strings_and_chars/1_intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ Before we go further, two words that show up everywhere in Rust:

The ownership model is why Rust has two string types in the first place: it tracks who owns each piece of data.
The 30-second version: every value has one owner, the value is dropped when that owner goes out of scope, and you can borrow a value without taking it.
The next chapter (moves and `Copy`) and the borrowing chapter make this hands-on, and a later memory chapter ties it together.
For now just keep the mental picture of "one owner, many short-lived borrows."
Next we'll move `String` values and copy integers, then add borrowing after functions and revisit the full memory model later.
For now, keep the mental picture of "one owner, many short-lived borrows."

The split between `&str` and `String` is what makes Rust strings both fast and safe.
A function that just *reads* text takes `&str`; a function that *produces* new text returns `String`.
Expand All @@ -48,26 +48,26 @@ For character counts use `s.chars().count()`.
UTF-8 means a single visible character can take more than one byte.

You'll also meet `.chars()` a lot.
It returns an iterator of `char`, and iterators have many useful adapters like `.next()`, `.count()`, and `.any(...)` (more on iterators in the iterators chapter).
It lets you walk through the `char` values in a string, one at a time.
Rust calls the value it returns an *iterator*, but you do not need the full iterator model yet.
We'll spend more time with methods such as `.next()`, `.count()`, and `.any(...)` later.

## Building a `String` with `format!`

The fastest way to assemble a new `String` is the `format!` macro.
A convenient way to assemble a new `String` is the `format!` macro.
It works like `println!`, except instead of printing, it returns the formatted text:

```rust
let name = "Alice";
let greeting: String = format!("Hello, {name}!");
```

A few things worth noticing:

- The `{name}` inside the string is a **captured identifier**.
Rust pulls the variable from the surrounding scope.
(Pre-2021 code uses `format!("Hello, {}!", name)` instead; both still work.)
- The macro returns a `String`, ready to return from your function.
- The exclamation mark (`!`) means it's a macro, not a regular function call.
You'll learn what that distinction buys you later; for now, treat it as a quirky bit of punctuation.
In the format string, `{name}` is a **captured identifier**.
Rust pulls the variable from the surrounding scope.
Pre-2021 code often writes `format!("Hello, {}!", name)` instead, and both forms still work.
The macro returns a `String`, ready to return from your function.
The exclamation mark (`!`) means `format!` is a macro rather than a regular function call.
We'll see what that distinction buys us later, so for now you can treat it as a quirky bit of punctuation.

## A note on `for` loops

Expand All @@ -79,14 +79,15 @@ for c in "hello".chars() {
}
```

You can read it as "for each `c` produced by the iterator on the right, run the body once."
You can read it as "for each `c` produced on the right, run the body once."
The loop variable is a fresh binding scoped to each iteration.
Anything that produces an iterator (a `Vec`, a slice, a `HashMap`, `0..10`, ...) works on the right-hand side.
Ranges, arrays, and collections can also go on the right-hand side because each can produce an iterator.
For now, it is enough to recognize that `.chars()` gives a `for` loop characters to walk through.

## Where to look things up

You won't memorize Rust's `std` library, and you don't need to.
Two things you can open in separate tabs right now:
Keep these two reference pages handy:

- [`std::fmt`](https://doc.rust-lang.org/std/fmt/) contains everything the formatting macros can do (padding, precision, hex, debug output…).
- [`str`](https://doc.rust-lang.org/std/primitive.str.html): the inventory of operations available on any `&str`.
2 changes: 1 addition & 1 deletion examples/01_strings_and_chars/3_count_chars.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Counting characters

Your first encounter with `&str`.
This is where byte length and character count part ways.
In many languages, asking for the "length" of a string gives you back the number of characters.
In Rust, `str::len` returns the number of *bytes* in the underlying UTF-8 buffer, which only matches the character count for plain ASCII.

Expand Down
6 changes: 3 additions & 3 deletions examples/01_strings_and_chars/4_shout.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Borrow in, own out

This step is the canonical "borrowed in, owned out" pattern.
The caller hands you a cheap `&str` view, and you give back a brand new `String` that they get to keep.
You'll see this pattern over and over in real Rust code, so it's worth getting comfortable with the signature now.
Here you borrow text to read it, then return a new `String` that the caller can keep.
The signature captures that handoff: `&str` in, owned `String` out.
You'll see the same shape whenever a function reads existing text to produce different text.

## Useful from the standard library

Expand Down
5 changes: 3 additions & 2 deletions examples/01_strings_and_chars/5_has_uppercase.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Iterating over characters

Strings aren't directly indexable in Rust (because UTF-8 characters have varying widths), but you can iterate over their `char`s.
A plain `for c in text.chars()` loop will work, and so will the iterator combinators like `any` or `find`, which usually express "is there at least one ..." checks more directly.
Strings aren't directly indexable in Rust because UTF-8 characters have varying widths, but you can walk through their `char`s.
A plain `for c in text.chars()` loop works.
When the question is "does at least one character match?", `any` expresses that directly and can stop as soon as it finds one.

## Useful from the standard library

Expand Down
14 changes: 7 additions & 7 deletions examples/02_moves_and_copy/1_intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ println!("{s}"); // ERROR: borrow of moved value: `s`
```

Assigning `s` to `t` *moves* the string.
There's now one owner, `t`, and `s` is dead.
Reach for `s` again and the compiler refuses, naming the exact line.
There's now one owner, `t`, and `s` no longer names a value you can use.
Reach for `s` again and the compiler points to the exact move that made it unavailable.
In a language with shared mutable pointers this would be a silent bug (two variables aliasing one buffer, one of them freeing it first), and Rust turns it into a compile error.

Why move instead of copy?
Expand All @@ -30,9 +30,9 @@ let b = a; // a is copied, not moved
println!("{a} {b}"); // both fine
```

The rule of thumb: a type that owns nothing on the heap and is cheap to duplicate is `Copy`, and the move rules never bite it.
Everything else moves.
For now, expect small primitive values such as integers and `bool` to be `Copy`, while heap-owning values such as `String` move.
A type's documentation tells you whether it implements `Copy` when the distinction is not obvious.

This is the first chapter where moves matter, because `String` is the first heap-owning type you've met.
Borrowing, using a value without taking it, is the next chapter.
Here we just get comfortable with ownership changing hands.
Moves matter now because `String` is the first heap-owning type you've met.
Here we'll get comfortable with ownership changing hands.
After functions, we'll borrow values so a caller can keep ownership while another function uses them.
9 changes: 5 additions & 4 deletions examples/02_moves_and_copy/2_take_ownership.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

When a function parameter has an owned type like `String` (no `&` in front), calling the function *moves* the argument in.
The caller's binding is no longer usable afterwards.
The value lives at the callee now, and will be dropped when the callee finishes (unless it hands ownership back via the return value, which is exactly what happens here).
The value now belongs to the called function.
It will be dropped when that function finishes unless the function hands ownership back, which is exactly what the return value does here.

Note the signature: `String` in, `String` out.
Implement the body by mutating the parameter (`s.push_str(...)`) and then returning `s`.
Because you own `s`, you're free to mutate it directly: ownership implies the right to modify.
Read the signature as `String` in, `String` out.
The function can choose to mutate the value it owns, but the parameter binding still needs `mut` before you can change it.
Append the text to `s`, then return the same `String` so the caller owns it again.

## Useful from the standard library

Expand Down
2 changes: 1 addition & 1 deletion examples/02_moves_and_copy/4_what_we_learned.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ You moved a `String` into a function and back out, and saw that an `i32` copies
Rust makes deep copies explicit through `.clone()`.
- `Copy` types (integers, `bool`, `char`, fixed-size arrays of those) duplicate bit-for-bit instead of moving, so the original stays usable.
- The owner is responsible for the value: when it goes out of scope, the value is dropped, with no garbage collector involved.
- Borrowing, coming up next, lets you hand a value to a function without giving up ownership at all.
- When we get to borrowing, you'll hand a value to a function without giving up ownership at all.
4 changes: 2 additions & 2 deletions examples/03_conditionals_and_loops/5_count_evens.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ The parameter here is a `&[i32]`, a *slice*: a borrowed view over a sequence of
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.
A `for` loop gives you each number in turn.
Keep a counter and bump it whenever the number is even.

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`.
Loading
Loading