100 exercises to learn Rust
This commit is contained in:
20
book/src/02_basic_calculator/00_intro.md
Normal file
20
book/src/02_basic_calculator/00_intro.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# A Basic Calculator
|
||||
|
||||
In this chapter we'll learn how to use Rust as a **calculator**.
|
||||
It might not sound like much, but it'll give us a chance to cover a lot of Rust's basics, such as:
|
||||
|
||||
- How to define and call functions
|
||||
- How to declare and use variables
|
||||
- Primitive types (integers and booleans)
|
||||
- Arithmetic operators (including overflow and underflow behavior)
|
||||
- Comparison operators
|
||||
- Control flow
|
||||
- Panics
|
||||
|
||||
Nailing the basics with a few exercises will get the language flowing under your fingers.
|
||||
When we move on to more complex topics, such as traits and ownership, you'll be able to focus on the new concepts
|
||||
without getting bogged down by the syntax or other trivial details.
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/02_basic_calculator/00_intro`
|
||||
138
book/src/02_basic_calculator/01_integers.md
Normal file
138
book/src/02_basic_calculator/01_integers.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# Types, part 1
|
||||
|
||||
In the ["Syntax" section](../01_intro/01_syntax.md) `compute`'s input parameters were of type `u32`.
|
||||
Let's unpack what that _means_.
|
||||
|
||||
## Primitive types
|
||||
|
||||
`u32` is one of Rust's **primitive types**. Primitive types are the most basic building blocks of a language.
|
||||
They're built into the language itself—i.e. they are not defined in terms of other types.
|
||||
|
||||
You can combine these primitive types to create more complex types. We'll see how soon enough.
|
||||
|
||||
## Integers
|
||||
|
||||
`u32`, in particular, is an **unsigned 32-bit integer**.
|
||||
|
||||
An integer is a number that can be written without a fractional component. E.g. `1` is an integer, while `1.2` is not.
|
||||
|
||||
### Signed vs. unsigned
|
||||
|
||||
An integer can be **signed** or **unsigned**.
|
||||
An unsigned integer can only represent non-negative numbers (i.e. `0` or greater).
|
||||
A signed integer can represent both positive and negative numbers (e.g. `-1`, `12`, etc.).
|
||||
|
||||
The `u` in `u32` stands for **unsigned**.
|
||||
The equivalent type for signed integer is `i32`, where the `i` stands for integer (i.e. any integer, positive or
|
||||
negative).
|
||||
|
||||
### Bit width
|
||||
|
||||
The `32` in `u32` refers to the **number of bits[^bit]** used to represent the number in memory.
|
||||
The more bits, the larger the range of numbers that can be represented.
|
||||
|
||||
Rust supports multiple bit widths for integers: `8`, `16`, `32`, `64`, `128`.
|
||||
|
||||
With 32 bits, `u32` can represent numbers from `0` to `2^32 - 1` (a.k.a. [`u32::MAX`](https://doc.rust-lang.org/std/primitive.u32.html#associatedconstant.MAX)).
|
||||
With the same number of bits, a signed integer (`i32`) can represent numbers from `-2^31` to `2^31 - 1`
|
||||
(i.e. from [`i32::MIN`](https://doc.rust-lang.org/std/primitive.i32.html#associatedconstant.MIN)
|
||||
to [`i32::MAX`](https://doc.rust-lang.org/std/primitive.i32.html#associatedconstant.MAX)).
|
||||
The maximum value for `i32` is smaller than the maximum value for `u32` because one bit is used to represent
|
||||
the sign of the number. Check out the [two's complement](https://en.wikipedia.org/wiki/Two%27s_complement)
|
||||
representation for more details on how signed integers are represented in memory.
|
||||
|
||||
### Summary
|
||||
|
||||
Combining the two variables (signed/unsigned and bit width), we get the following integer types:
|
||||
|
||||
| Bit width | Signed | Unsigned |
|
||||
|-----------|--------|----------|
|
||||
| 8-bit | `i8` | `u8` |
|
||||
| 16-bit | `i16` | `u16` |
|
||||
| 32-bit | `i32` | `u32` |
|
||||
| 64-bit | `i64` | `u64` |
|
||||
| 128-bit | `i128` | `u128` |
|
||||
|
||||
## Literals
|
||||
|
||||
A **literal** is a notation for representing a fixed value in source code.
|
||||
For example, `42` is a Rust literal for the number forty-two.
|
||||
|
||||
### Type annotations for literals
|
||||
|
||||
But all values in Rust have a type, so... what's the type of `42`?
|
||||
|
||||
The Rust compiler will try to infer the type of a literal based on how it's used.
|
||||
If you don't provide any context, the compiler will default to `i32` for integer literals.
|
||||
If you want to use a different type, you can add the desired integer type as a suffix—e.g. `2u64` is a 2 that's
|
||||
explicitly typed as a `u64`.
|
||||
|
||||
### Underscores in literals
|
||||
|
||||
You can use underscores `_` to improve the readability of large numbers.
|
||||
For example, `1_000_000` is the same as `1000000`.
|
||||
|
||||
## Arithmetic operators
|
||||
|
||||
Rust supports the following arithmetic operators[^traits] for integers:
|
||||
|
||||
- `+` for addition
|
||||
- `-` for subtraction
|
||||
- `*` for multiplication
|
||||
- `/` for division
|
||||
- `%` for remainder
|
||||
|
||||
Precedence and associativity rules for these operators are the same as in mathematics.
|
||||
You can use parentheses to override the default precedence. E.g. `2 * (3 + 4)`.
|
||||
|
||||
> ⚠️ **Warning**
|
||||
>
|
||||
> The division operator `/` performs integer division when used with integer types.
|
||||
> I.e. the result is truncated towards zero. For example, `5 / 2` is `2`, not `2.5`.
|
||||
|
||||
## No automatic type coercion
|
||||
|
||||
As we discussed in the previous exercise, Rust is a statically typed language.
|
||||
In particular, Rust is quite strict about type coercion. It won't automatically convert a value from one type to
|
||||
another[^coercion],
|
||||
even if the conversion is lossless. You have to do it explicitly.
|
||||
|
||||
For example, you can't assign a `u8` value to a variable with type `u32`, even though all `u8` values are valid `u32`
|
||||
values:
|
||||
|
||||
```rust
|
||||
let b: u8 = 100;
|
||||
let a: u32 = b;
|
||||
```
|
||||
|
||||
It'll throw a compilation error:
|
||||
|
||||
```text
|
||||
error[E0308]: mismatched types
|
||||
|
|
||||
3 | let a: u32 = b;
|
||||
| --- ^ expected `u32`, found `u8`
|
||||
| |
|
||||
| expected due to this
|
||||
|
|
||||
```
|
||||
|
||||
We'll see how to convert between types [later in this course](../04_traits/08_from).
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/02_basic_calculator/01_integers`
|
||||
|
||||
## Further reading
|
||||
|
||||
- [The integer types section](https://doc.rust-lang.org/book/ch03-02-data-types.html#integer-types) in the official Rust book
|
||||
|
||||
[^bit]: A bit is the smallest unit of data in a computer. It can only have two values: `0` or `1`.
|
||||
|
||||
[^traits]: Rust doesn't let you define custom operators, but it puts you in control of how the built-in operators
|
||||
behave.
|
||||
We'll talk about operator overloading [later in the course](../04_traits/03_operator_overloading), after we've covered traits.
|
||||
|
||||
[^coercion]: There are some exceptions to this rule, mostly related to references, smart pointers and ergonomics. We'll
|
||||
cover those [later on](../04_traits/06_deref).
|
||||
A mental model of "all conversions are explicit" will serve you well in the meantime.
|
||||
104
book/src/02_basic_calculator/02_variables.md
Normal file
104
book/src/02_basic_calculator/02_variables.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# Variables
|
||||
|
||||
In Rust, you can use the `let` keyword to declare **variables**.
|
||||
For example:
|
||||
|
||||
```rust
|
||||
let x = 42;
|
||||
```
|
||||
|
||||
Above we defined a variable `x` and assigned it the value `42`.
|
||||
|
||||
## Type
|
||||
|
||||
Every variable in Rust must have a type. It can either be inferred by the compiler or explicitly specified by the
|
||||
developer.
|
||||
|
||||
### Explicit type annotation
|
||||
|
||||
You can specify the variable type by adding a colon `:` followed by the type after the variable name. For example:
|
||||
|
||||
```rust
|
||||
// let <variable_name>: <type> = <expression>;
|
||||
let x: u32 = 42;
|
||||
```
|
||||
|
||||
In the example above, we explicitly constrained the type of `x` to be `u32`.
|
||||
|
||||
### Type inference
|
||||
|
||||
If we don't specify the type of a variable, the compiler will try to infer it based on the context in which the variable
|
||||
is used.
|
||||
|
||||
```rust
|
||||
let x = 42;
|
||||
let y: u32 = x;
|
||||
```
|
||||
|
||||
In the example above, we didn't specify the type of `x`.
|
||||
`x` is later assigned to `y`, which is explicitly typed as `u32`. Since Rust doesn't perform automatic type coercion,
|
||||
the compiler infers the type of `x` to be `u32`—the same as `y` and the only type that will allow the program to compile
|
||||
without errors.
|
||||
|
||||
### Inference limitations
|
||||
|
||||
The compiler sometimes needs a little help to infer the correct variable type based on its usage.
|
||||
In those cases you'll get a compilation error and the compiler will ask you to provide an explicit type hint to
|
||||
disambiguate the situation.
|
||||
|
||||
## Function arguments are variables
|
||||
|
||||
Not all heroes wear capes, not all variables are declared with `let`.
|
||||
Function arguments are variables too!
|
||||
|
||||
```rust
|
||||
fn add_one(x: u32) -> u32 {
|
||||
x + 1
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, `x` is a variable of type `u32`.
|
||||
The only difference between `x` and a variable declared with `let` is that functions arguments **must** have their type
|
||||
explicitly declared. The compiler won't infer it for you.
|
||||
This constraint allows the Rust compiler (and us humans!) to understand the function's signature without having to look
|
||||
at its implementation. That's a big boost for compilation speed[^speed]!
|
||||
|
||||
## Initialization
|
||||
|
||||
You don't have to initialize a variable when you declare it.
|
||||
For example
|
||||
|
||||
```rust
|
||||
let x: u32;
|
||||
```
|
||||
|
||||
is a valid variable declaration.
|
||||
However, you must initialize the variable before using it. The compiler will throw an error if you don't:
|
||||
|
||||
```rust
|
||||
let x: u32;
|
||||
let y = x + 1;
|
||||
```
|
||||
|
||||
will throw a compilation error:
|
||||
|
||||
```text
|
||||
error[E0381]: used binding `x` isn't initialized
|
||||
--> src/main.rs:3:9
|
||||
|
|
||||
2 | let x: u32;
|
||||
| - binding declared here but left uninitialized
|
||||
3 | let y = x + 1;
|
||||
| ^ `x` used here but it isn't initialized
|
||||
|
|
||||
help: consider assigning a value
|
||||
|
|
||||
2 | let x: u32 = 0;
|
||||
| +++
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/02_basic_calculator/02_variables`
|
||||
|
||||
[^speed]: The Rust compiler needs all the help it can get when it comes to compilation speed.
|
||||
85
book/src/02_basic_calculator/03_if_else.md
Normal file
85
book/src/02_basic_calculator/03_if_else.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Control flow, part 1
|
||||
|
||||
All our programs so far have been pretty straightforward.
|
||||
A sequence of instructions is executed from top to bottom, and that's it.
|
||||
|
||||
It's time to introduce some **branching**.
|
||||
|
||||
## `if` expressions
|
||||
|
||||
The `if` keyword is used to execute a block of code only if a condition is true.
|
||||
|
||||
Here's a simple example:
|
||||
|
||||
```rust
|
||||
let number = 3;
|
||||
if number < 5 {
|
||||
println!("`number` is smaller than 5");
|
||||
}
|
||||
```
|
||||
|
||||
This program will print `number is smaller than 5` because the condition `number < 5` is true.
|
||||
|
||||
Like most programming languages, Rust supports an optional `else` branch to execute a block of code when the condition in an
|
||||
`if` expression is false.
|
||||
For example:
|
||||
|
||||
```rust
|
||||
let number = 3;
|
||||
|
||||
if number < 5 {
|
||||
println!("`number` is smaller than 5");
|
||||
} else {
|
||||
println!("`number` is greater than or equal to 5");
|
||||
}
|
||||
```
|
||||
|
||||
## Booleans
|
||||
|
||||
The condition in an `if` expression must be of type `bool`, a **boolean**.
|
||||
Booleans, just like integers, are a primitive type in Rust.
|
||||
|
||||
A boolean can have one of two values: `true` or `false`.
|
||||
|
||||
### No truthy or falsy values
|
||||
|
||||
If the condition in an `if` expression is not a boolean, you'll get a compilation error.
|
||||
|
||||
For example, the following code will not compile:
|
||||
|
||||
```rust
|
||||
let number = 3;
|
||||
if number {
|
||||
println!("`number` is not zero");
|
||||
}
|
||||
```
|
||||
|
||||
You'll get the following compilation error:
|
||||
|
||||
```text
|
||||
error[E0308]: mismatched types
|
||||
--> src/main.rs:3:8
|
||||
|
|
||||
3 | if number {
|
||||
| ^^^^^^ expected `bool`, found integer
|
||||
```
|
||||
|
||||
This follows from Rust's philosophy around type coercion: there's no automatic conversion from non-boolean types to booleans.
|
||||
Rust doesn't have the concept of **truthy** or **falsy** values, like JavaScript or Python.
|
||||
You have to be explicit about the condition you want to check.
|
||||
|
||||
### Comparison operators
|
||||
|
||||
It's quite common to use comparison operators to build conditions for `if` expressions.
|
||||
Here are the comparison operators available in Rust when working with integers:
|
||||
|
||||
- `==`: equal to
|
||||
- `!=`: not equal to
|
||||
- `<`: less than
|
||||
- `>`: greater than
|
||||
- `<=`: less than or equal to
|
||||
- `>=`: greater than or equal to
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/02_basic_calculator/03_if_else`
|
||||
58
book/src/02_basic_calculator/04_panics.md
Normal file
58
book/src/02_basic_calculator/04_panics.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Panics
|
||||
|
||||
Let's go back to the `speed` function you wrote for the ["Variables" section](../02_variables/README.md).
|
||||
It probably looked something like this:
|
||||
|
||||
```rust
|
||||
fn speed(start: u32, end: u32, time_elapsed: u32) -> u32 {
|
||||
let distance = end - start;
|
||||
distance / time_elapsed
|
||||
}
|
||||
```
|
||||
|
||||
If you have a keen eye, you might have spotted one issue[^one]: what happens if `time_elapsed` is zero?
|
||||
|
||||
You can try it
|
||||
out [on the Rust playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=36e5ddbe3b3f741dfa9f74c956622bac)!
|
||||
The program will exit with the following error message:
|
||||
|
||||
```text
|
||||
thread 'main' panicked at src/main.rs:3:5:
|
||||
attempt to divide by zero
|
||||
```
|
||||
|
||||
This is known as a **panic**.
|
||||
A panic is Rust's way to signal that something went so wrong that
|
||||
the program can't continue executing, it's an **unrecoverable error**[^catching]. Division by zero classifies as such an
|
||||
error.
|
||||
|
||||
## The panic! macro
|
||||
|
||||
You can intentionally trigger a panic by calling the `panic!` macro[^macro]:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
panic!("This is a panic!");
|
||||
// The line below will never be executed
|
||||
let x = 1 + 2;
|
||||
}
|
||||
```
|
||||
|
||||
There are other mechanism to work with recoverable errors in Rust, which [we'll cover later](../05_ticket_v2/06_fallibility).
|
||||
For the time being we'll stick with panics as a brutal but simple stopgap solution.
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/02_basic_calculator/04_panics`
|
||||
|
||||
## Further reading
|
||||
|
||||
- [The panic! macro documentation](https://doc.rust-lang.org/std/macro.panic.html)
|
||||
|
||||
[^one]: There's another issue with `speed` that we'll address soon enough. Can you spot it?
|
||||
|
||||
[^catching]: You can try to catch a panic, but it should be a last resort attempt reserved for very specific
|
||||
circumstances.
|
||||
|
||||
[^macro]: If it's followed by a `!`, it's a macro invocation. Think of macros as spicy functions for now. We'll
|
||||
cover them in more detail later in the course.
|
||||
15
book/src/02_basic_calculator/05_factorial.md
Normal file
15
book/src/02_basic_calculator/05_factorial.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Factorial
|
||||
|
||||
So far you've learned:
|
||||
|
||||
- How to define a function
|
||||
- How to call a function
|
||||
- Which integer types are available in Rust
|
||||
- Which arithmetic operators are available for integers
|
||||
- How to execute conditional logic via comparisons and `if`/`else` expressions
|
||||
|
||||
It looks like you're ready to tackle factorials!
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/02_basic_calculator/05_factorial`
|
||||
89
book/src/02_basic_calculator/06_while.md
Normal file
89
book/src/02_basic_calculator/06_while.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Loops, part 1: `while`
|
||||
|
||||
Your implementation of `factorial` has been forced to use recursion.
|
||||
This may feel natural to you, especially if you're coming from a functional programming background.
|
||||
Or it may feel strange, if you're used to more imperative languages like C or Python.
|
||||
|
||||
Let's see how you can implement the same functionality using a **loop** instead.
|
||||
|
||||
## The `while` loop
|
||||
|
||||
A `while` loop is a way to execute a block of code as long as a **condition** is true.
|
||||
Here's the general syntax:
|
||||
|
||||
```rust
|
||||
while <condition> {
|
||||
// code to execute
|
||||
}
|
||||
```
|
||||
|
||||
For example, we might want to sum the numbers from 1 to 5:
|
||||
|
||||
```rust
|
||||
let sum = 0;
|
||||
let i = 1;
|
||||
// "while i is less than or equal to 5"
|
||||
while i <= 5 {
|
||||
// `+=` is a shorthand for `sum = sum + i`
|
||||
sum += i;
|
||||
i += 1;
|
||||
}
|
||||
```
|
||||
|
||||
This will keep adding 1 to `sum` until `i` is no longer less than or equal to 5.
|
||||
|
||||
## The `mut` keyword
|
||||
|
||||
The example above won't compile as is. You'll get an error like:
|
||||
|
||||
```text
|
||||
error[E0384]: cannot assign twice to immutable variable `sum`
|
||||
--> src/main.rs:7:9
|
||||
|
|
||||
2 | let sum = 0;
|
||||
| ---
|
||||
| |
|
||||
| first assignment to `sum`
|
||||
| help: consider making this binding mutable: `mut sum`
|
||||
...
|
||||
7 | sum += i;
|
||||
| ^^^^^^^^ cannot assign twice to immutable variable
|
||||
|
||||
error[E0384]: cannot assign twice to immutable variable `i`
|
||||
--> src/main.rs:8:9
|
||||
|
|
||||
3 | let i = 1;
|
||||
| -
|
||||
| |
|
||||
| first assignment to `i`
|
||||
| help: consider making this binding mutable: `mut i`
|
||||
...
|
||||
8 | i += 1;
|
||||
| ^^^^^^ cannot assign twice to immutable variable
|
||||
```
|
||||
|
||||
This is because variables in Rust are **immutable** by default.
|
||||
You can't change their value once it has been assigned.
|
||||
|
||||
If you want to allow modifications, you have to declare the variable as **mutable** using the `mut` keyword:
|
||||
|
||||
```rust
|
||||
// `sum` and `i` are mutable now!
|
||||
let mut sum = 0;
|
||||
let mut i = 1;
|
||||
|
||||
while i <= 5 {
|
||||
sum += i;
|
||||
i += 1;
|
||||
}
|
||||
```
|
||||
|
||||
This will compile and run without errors.
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/02_basic_calculator/06_while`
|
||||
|
||||
## Further reading
|
||||
|
||||
- [`while` loop documentation](https://doc.rust-lang.org/std/keyword.while.html)
|
||||
68
book/src/02_basic_calculator/07_for.md
Normal file
68
book/src/02_basic_calculator/07_for.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Loops, part 2: `for`
|
||||
|
||||
Having to manually increment a counter variable is somewhat tedious. The pattern is also extremely common!
|
||||
To make this easier, Rust provides a more concise way to iterate over a range of values: the `for` loop.
|
||||
|
||||
## The `for` loop
|
||||
|
||||
A `for` loop is a way to execute a block of code for each element in an iterator[^iterator].
|
||||
|
||||
Here's the general syntax:
|
||||
|
||||
```rust
|
||||
for <element> in <iterator> {
|
||||
// code to execute
|
||||
}
|
||||
```
|
||||
|
||||
## Ranges
|
||||
|
||||
Rust's standard library provides **range** type that can be used to iterate over a sequence of numbers[^weird-ranges].
|
||||
|
||||
For example, if we want to sum the numbers from 1 to 5:
|
||||
|
||||
```rust
|
||||
let mut sum = 0;
|
||||
for i in 1..=5 {
|
||||
sum += i;
|
||||
}
|
||||
```
|
||||
|
||||
Every time the loop runs, `i` will be assigned the next value in the range before executing the block of code.
|
||||
|
||||
There are five kinds of ranges in Rust:
|
||||
|
||||
- `1..5`: A (half-open) range. It includes all numbers from 1 to 4. It doesn't include the last value, 5.
|
||||
- `1..=5`: An inclusive range. It includes all numbers from 1 to 5. It includes the last value, 5.
|
||||
- `1..`: An open-ended range. It includes all numbers from 1 to infinity (well, until the maximum value of the integer type).
|
||||
- `..5`: A range that starts at the minimum value for the integer type and ends at 4. It doesn't include the last value, 5.
|
||||
- `..=5`: A range that starts at the minimum value for the integer type and ends at 5. It includes the last value, 5.
|
||||
|
||||
You can use a `for` loop with the first three kinds of ranges, where the starting point
|
||||
is explicitly specified. The last two range types are used in other contexts, that we'll cover later.
|
||||
|
||||
The extreme values of a range don't have to be integer literals—they can be variables or expressions too!
|
||||
|
||||
For example:
|
||||
|
||||
```rust
|
||||
let end = 5;
|
||||
let mut sum = 0;
|
||||
|
||||
for i in 1..(end + 1) {
|
||||
sum += i;
|
||||
}
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/02_basic_calculator/07_for`
|
||||
|
||||
## Further reading
|
||||
|
||||
- [`for` loop documentation](https://doc.rust-lang.org/std/keyword.for.html)
|
||||
|
||||
[^iterator]: Later in the course we'll give a precise definition of what counts as an "iterator".
|
||||
For now, think of it as a sequence of values that you can loop over.
|
||||
[^weird-ranges]: You can use ranges with other types too (e.g. characters and IP addresses),
|
||||
but integers are definitely the most common case in day-to-day Rust programming.
|
||||
112
book/src/02_basic_calculator/08_overflow.md
Normal file
112
book/src/02_basic_calculator/08_overflow.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Overflow
|
||||
|
||||
The factorial of a number grows quite fast.
|
||||
For example, the factorial of 20 is 2,432,902,008,176,640,000. That's already bigger than the maximum value for a
|
||||
32-bit integer, 2,147,483,647.
|
||||
|
||||
When the result of an arithmetic operation is bigger than the maximum value for a given integer type,
|
||||
we are talking about **an integer overflow**.
|
||||
|
||||
Integer overflows are an issue because they violate the contract for arithmetic operations.
|
||||
The result of an arithmetic operation between two integers of a given type should be another integer of the same type.
|
||||
But the _mathematically correct result_ doesn't fit into that integer type!
|
||||
|
||||
> If the result is smaller than the minimum value for a given integer type, we refer to the event as **an integer
|
||||
> underflow**.
|
||||
> For brevity, we'll only talk about integer overflows for the rest of this section, but keep in mind that
|
||||
> everything we say applies to integer underflows as well.
|
||||
>
|
||||
> The `speed` function you wrote in the ["Variables" section](02_variables.md) underflowed for some input
|
||||
> combinations.
|
||||
> E.g. if `end` is smaller than `start`, `end - start` will underflow the `u32` type since the result is supposed
|
||||
> to be negative but `u32` can't represent negative numbers.
|
||||
|
||||
## No automatic promotion
|
||||
|
||||
One possible approach would be automatically promote the result to a bigger integer type.
|
||||
E.g. if you're summing two `u8` integers and the result is 256 (`u8::MAX + 1`), Rust could choose to interpret the
|
||||
result as `u16`, the next integer type that's big enough to hold 256.
|
||||
|
||||
But, as we've discussed before, Rust is quite picky about type conversions. Automatic integer promotion
|
||||
is not Rust's solution to the integer overflow problem.
|
||||
|
||||
## Alternatives
|
||||
|
||||
Since we ruled out automatic promotion, what can we do when an integer overflow occurs?
|
||||
It boils down to two different approaches:
|
||||
|
||||
- Reject the operation
|
||||
- Come up with a "sensible" result that fits into the expected integer type
|
||||
|
||||
### Reject the operation
|
||||
|
||||
This is the most conservative approach: we stop the program when an integer overflow occurs.
|
||||
That's done via a panic, the mechanism we've already seen in the ["Panics" section](04_panics.md).
|
||||
|
||||
### Come up with a "sensible" result
|
||||
|
||||
When the result of an arithmetic operation is bigger than the maximum value for a given integer type, you can
|
||||
choose to **wrap around**.
|
||||
If you think of all the possible values for a given integer type as a circle, wrapping around means that when you
|
||||
reach the maximum value, you start again from the minimum value.
|
||||
|
||||
For example, if you do a **wrapping addition** between 1 and 255 (=`u8::MAX`), the result is 0 (=`u8::MIN`).
|
||||
If you're working with signed integers, the same principle applies. E.g. adding 1 to 127 (=`i8::MAX`) with wrapping
|
||||
will give you -128 (=`i8::MIN`).
|
||||
|
||||
## `overflow-checks`
|
||||
|
||||
Rust lets you, the developer, choose which approach to use when an integer overflow occurs.
|
||||
The behaviour is controlled by the `overflow-checks` profile setting.
|
||||
|
||||
If `overflow-checks` is set to `true`, Rust will **panic at runtime** when an integer operation overflows.
|
||||
If `overflow-checks` is set to `false`, Rust will **wrap around** when an integer operation overflows.
|
||||
|
||||
You may be wondering—what is a profile setting? Let's get into that!
|
||||
|
||||
## Profiles
|
||||
|
||||
A [**profile**](https://doc.rust-lang.org/cargo/reference/profiles.html) is a set of configuration options that can be
|
||||
used to customize the way Rust code is compiled.
|
||||
|
||||
Cargo provides two built-in profiles: `dev` and `release`.
|
||||
The `dev` profile is used every time you run `cargo build`, `cargo run` or `cargo test`. It's aimed at local
|
||||
development,
|
||||
therefore it sacrifices runtime performance in favor of faster compilation times and a better debugging experience.
|
||||
The `release` profile, instead, is optimized for runtime performance but incurs longer compilation times. You need
|
||||
to explicitly request via the `--release` flag—e.g. `cargo build --release` or `cargo run --release`.
|
||||
|
||||
> "Have you built your project in release mode?" is almost a meme in the Rust community.
|
||||
> It refers to developers who are not familiar with Rust and complain about its performance on
|
||||
> social media (e.g. Reddit, Twitter, etc.) before realizing they haven't built their project in
|
||||
> release mode.
|
||||
|
||||
You can also define custom profiles or customize the built-in ones.
|
||||
|
||||
### `overflow-check`
|
||||
|
||||
By default, `overflow-checks` is set to:
|
||||
|
||||
- `true` for the `dev` profile
|
||||
- `false` for the `release` profile
|
||||
|
||||
This is in line with the goals of the two profiles.
|
||||
`dev` is aimed at local development, so it panics in order to highlight potential issues as early as possible.
|
||||
`release`, instead, is tuned for runtime performance: checking for overflows would slow down the program, so it
|
||||
prefers to wrap around.
|
||||
|
||||
At the same time, having different behaviours for the two profiles can lead to subtle bugs.
|
||||
Our recommendation is to enable `overflow-checks` for both profiles: it's better to crash than to silently produce
|
||||
incorrect results. The runtime performance hit is negligible in most cases; if you're working on a performance-critical
|
||||
application, you can run benchmarks to decide if it's something you can afford.
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/02_basic_calculator/08_overflow`
|
||||
|
||||
## Further reading
|
||||
|
||||
- Check out ["Myths and legends about integer overflow in Rust"](https://huonw.github.io/blog/2016/04/myths-and-legends-about-integer-overflow-in-rust/)
|
||||
for an in-depth discussion about integer overflow in Rust.
|
||||
|
||||
[^catching]: You can try to catch a panic, but it should be a last resort reserved for very specific circumstances.
|
||||
43
book/src/02_basic_calculator/09_saturating.md
Normal file
43
book/src/02_basic_calculator/09_saturating.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Case-by-case behavior
|
||||
|
||||
`overflow-checks` is a blunt tool: it's a global setting that affects the whole program.
|
||||
It often happens that you want to handle integer overflows differently depending on the context: sometimes
|
||||
wrapping is the right choice, other times panicking is preferable.
|
||||
|
||||
## `wrapping_` methods
|
||||
|
||||
You can opt into wrapping arithmetic on a per-operation basis by using the `wrapping_` methods[^method].
|
||||
For example, you can use `wrapping_add` to add two integers with wrapping:
|
||||
|
||||
```rust
|
||||
let x = 255u8;
|
||||
let y = 1u8;
|
||||
let sum = x.wrapping_add(y);
|
||||
assert_eq!(sum, 0);
|
||||
```
|
||||
|
||||
## `saturating_` methods
|
||||
|
||||
Alternatively, you can opt into **saturating arithmetic** by using the `saturating_` methods.
|
||||
Instead of wrapping around, saturating arithmetic will return the maximum or minimum value for the integer type.
|
||||
For example:
|
||||
|
||||
```rust
|
||||
let x = 255u8;
|
||||
let y = 1u8;
|
||||
let sum = x.saturating_add(y);
|
||||
assert_eq!(sum, 255);
|
||||
```
|
||||
|
||||
Since `255 + 1` is `256`, which is bigger than `u8::MAX`, the result is `u8::MAX` (255).
|
||||
The opposite happens for underflows: `0 - 1` is `-1`, which is smaller than `u8::MIN`, so the result is `u8::MIN` (0).
|
||||
|
||||
You can't get saturating arithmetic via the `overflow-checks` profile setting—you have to explicitly opt into it
|
||||
when performing the arithmetic operation.
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/02_basic_calculator/09_saturating`
|
||||
|
||||
[^method]: You can think of methods as functions that are "attached" to a specific type.
|
||||
We'll cover methods (and how to define them) in the next chapter.
|
||||
105
book/src/02_basic_calculator/10_as_casting.md
Normal file
105
book/src/02_basic_calculator/10_as_casting.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Conversions, pt. 1
|
||||
|
||||
We've repeated over and over again that Rust won't perform
|
||||
implicit type conversions for integers.
|
||||
How do you perform _explicit_ conversions then?
|
||||
|
||||
## `as`
|
||||
|
||||
You can use the `as` operator to convert between integer types.
|
||||
`as` conversions are **infallible**.
|
||||
|
||||
For example:
|
||||
|
||||
```rust
|
||||
let a: u32 = 10;
|
||||
|
||||
// Cast `a` into the `u64` type
|
||||
let b = a as u64;
|
||||
|
||||
// You can use `_` as the target type
|
||||
// if it can be correctly inferred
|
||||
// by the compiler. For example:
|
||||
let c: u64 = a as _;
|
||||
```
|
||||
|
||||
The semantics of this conversion are what you expect: all `u32` values are valid `u64`
|
||||
values.
|
||||
|
||||
### Truncation
|
||||
|
||||
Things get more interesting if we go in the opposite direction:
|
||||
|
||||
```rust
|
||||
// A number that's too big
|
||||
// to fit into a `u8`
|
||||
let a: u16 = 255 + 1;
|
||||
let b = a as u8;
|
||||
```
|
||||
|
||||
This program will run without issues, because `as` conversions are infallible.
|
||||
But what is the value of `b`?
|
||||
When going from a larger integer type to a smaller, the Rust compiler will perform
|
||||
a **truncation**.
|
||||
|
||||
To understand what happens, let's start by looking at how `256u16` is
|
||||
represented in memory, as a sequence of bits:
|
||||
|
||||
```text
|
||||
0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0
|
||||
| | |
|
||||
+---------------+---------------+
|
||||
First 8 bits Last 8 bits
|
||||
```
|
||||
|
||||
When converting to a `u8`, the Rust compiler will keep the last 8 bits of a `u16`
|
||||
memory representation:
|
||||
|
||||
```text
|
||||
0 0 0 0 0 0 0 0
|
||||
| |
|
||||
+---------------+
|
||||
Last 8 bits
|
||||
```
|
||||
|
||||
Hence `256 as u8` is equal to `0`. That's... not ideal, in most scenarios.
|
||||
In fact, the Rust compiler will actively try to stop you if it sees you trying
|
||||
to cast a literal value which will result in a truncation:
|
||||
|
||||
```text
|
||||
error: literal out of range for `i8`
|
||||
|
|
||||
4 | let a = 255 as i8;
|
||||
| ^^^
|
||||
|
|
||||
= note: the literal `255` does not fit into the type `i8` whose range is `-128..=127`
|
||||
= help: consider using the type `u8` instead
|
||||
= note: `#[deny(overflowing_literals)]` on by default
|
||||
```
|
||||
|
||||
### Recommendation
|
||||
|
||||
As a rule of thumb, be quite careful with `as` casting.
|
||||
Use it _exclusively_ for going from a smaller type to a larger type.
|
||||
To convert from a larger to smaller integer type, rely on the
|
||||
[*fallible* conversion machinery](../05_ticket_v2/13_try_from) that we'll
|
||||
explore later in the course.
|
||||
|
||||
### Limitations
|
||||
|
||||
Surprising behaviour is not the only downside of `as` casting.
|
||||
It is also fairly limited: you can only rely on `as` casting
|
||||
for primitive types and a few other special cases.
|
||||
When working with composite types, you'll have to rely on
|
||||
different conversion mechanisms ([fallible](../05_ticket_v2/13_try_from)
|
||||
and [infallible](../04_traits/08_from)), which we'll explore later on.
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/02_basic_calculator/10_as_casting`
|
||||
|
||||
## Further reading
|
||||
|
||||
- Check out [Rust's official reference](https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast)
|
||||
to learn the precise behaviour of `as` casting for each source/target combination,
|
||||
as well as the exhaustive list of allowed conversions.
|
||||
Reference in New Issue
Block a user