@@ -1,6 +1,6 @@
|
||||
# A Basic Calculator
|
||||
|
||||
In this chapter we'll learn how to use Rust as a **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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Types, part 1
|
||||
|
||||
In the ["Syntax" section](../01_intro/01_syntax.md) `compute`'s input parameters were of type `u32`.
|
||||
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
|
||||
@@ -18,25 +18,25 @@ An integer is a number that can be written without a fractional component. E.g.
|
||||
|
||||
### Signed vs. unsigned
|
||||
|
||||
An integer can be **signed** or **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 `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 `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 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)).
|
||||
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.
|
||||
@@ -46,7 +46,7 @@ representation for more details on how signed integers are represented in memory
|
||||
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` |
|
||||
@@ -55,21 +55,21 @@ Combining the two variables (signed/unsigned and bit width), we get the followin
|
||||
|
||||
## Literals
|
||||
|
||||
A **literal** is a notation for representing a fixed value in source code.
|
||||
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.
|
||||
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.
|
||||
You can use underscores `_` to improve the readability of large numbers.\
|
||||
For example, `1_000_000` is the same as `1000000`.
|
||||
|
||||
## Arithmetic operators
|
||||
@@ -82,7 +82,7 @@ Rust supports the following arithmetic operators[^traits] for integers:
|
||||
- `/` for division
|
||||
- `%` for remainder
|
||||
|
||||
Precedence and associativity rules for these operators are the same as in mathematics.
|
||||
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**
|
||||
@@ -92,7 +92,7 @@ You can use parentheses to override the default precedence. E.g. `2 * (3 + 4)`.
|
||||
|
||||
## No automatic type coercion
|
||||
|
||||
As we discussed in the previous exercise, Rust is a statically typed language.
|
||||
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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Variables
|
||||
|
||||
In Rust, you can use the `let` keyword to declare **variables**.
|
||||
In Rust, you can use the `let` keyword to declare **variables**.\
|
||||
For example:
|
||||
|
||||
```rust
|
||||
@@ -35,20 +35,20 @@ let x = 42;
|
||||
let y: u32 = x;
|
||||
```
|
||||
|
||||
In the example above, we didn't specify the type of `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.
|
||||
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`.
|
||||
Not all heroes wear capes, not all variables are declared with `let`.\
|
||||
Function arguments are variables too!
|
||||
|
||||
```rust
|
||||
@@ -57,22 +57,22 @@ fn add_one(x: u32) -> u32 {
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, `x` is a variable of type `u32`.
|
||||
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.
|
||||
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.
|
||||
You don't have to initialize a variable when you declare it.\
|
||||
For example
|
||||
|
||||
```rust
|
||||
let x: u32;
|
||||
```
|
||||
|
||||
is a valid variable declaration.
|
||||
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
|
||||
@@ -101,4 +101,4 @@ help: consider assigning a value
|
||||
|
||||
- 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.
|
||||
[^speed]: The Rust compiler needs all the help it can get when it comes to compilation speed.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Control flow, part 1
|
||||
|
||||
All our programs so far have been pretty straightforward.
|
||||
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**.
|
||||
@@ -23,7 +23,7 @@ This program will print `number is smaller than 5` because the condition `number
|
||||
### `else` clauses
|
||||
|
||||
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.
|
||||
`if` expression is false.\
|
||||
For example:
|
||||
|
||||
```rust
|
||||
@@ -38,7 +38,7 @@ if number < 5 {
|
||||
|
||||
## Booleans
|
||||
|
||||
The condition in an `if` expression must be of type `bool`, a **boolean**.
|
||||
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`.
|
||||
@@ -67,12 +67,12 @@ error[E0308]: mismatched types
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
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
|
||||
@@ -84,7 +84,7 @@ Here are the comparison operators available in Rust when working with integers:
|
||||
|
||||
## `if/else` is an expression
|
||||
|
||||
In Rust, `if` expressions are **expressions**, not statements: they return a value.
|
||||
In Rust, `if` expressions are **expressions**, not statements: they return a value.\
|
||||
That value can be assigned to a variable or used in other expressions. For example:
|
||||
|
||||
```rust
|
||||
@@ -96,11 +96,10 @@ let message = if number < 5 {
|
||||
};
|
||||
```
|
||||
|
||||
In the example above, each branch of the `if` evaluates to a string literal,
|
||||
which is then assigned to the `message` variable.
|
||||
In the example above, each branch of the `if` evaluates to a string literal,
|
||||
which is then assigned to the `message` variable.\
|
||||
The only requirement is that both `if` branches return the same type.
|
||||
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/02_basic_calculator/03_if_else`
|
||||
- The exercise for this section is located in `exercises/02_basic_calculator/03_if_else`
|
||||
|
||||
@@ -13,7 +13,7 @@ fn speed(start: u32, end: u32, time_elapsed: u32) -> u32 {
|
||||
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)!
|
||||
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
|
||||
@@ -21,7 +21,7 @@ thread 'main' panicked at src/main.rs:3:5:
|
||||
attempt to divide by zero
|
||||
```
|
||||
|
||||
This is known as a **panic**.
|
||||
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.
|
||||
|
||||
@@ -12,4 +12,4 @@ It looks like you're ready to tackle factorials!
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/02_basic_calculator/05_factorial`
|
||||
- The exercise for this section is located in `exercises/02_basic_calculator/05_factorial`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Loops, part 1: `while`
|
||||
|
||||
Your implementation of `factorial` has been forced to use recursion.
|
||||
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.
|
||||
|
||||
@@ -8,7 +8,7 @@ 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.
|
||||
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
|
||||
@@ -62,7 +62,7 @@ error[E0384]: cannot assign twice to immutable variable `i`
|
||||
| ^^^^^^ cannot assign twice to immutable variable
|
||||
```
|
||||
|
||||
This is because variables in Rust are **immutable** by default.
|
||||
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:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Loops, part 2: `for`
|
||||
|
||||
Having to manually increment a counter variable is somewhat tedious. The pattern is also extremely common!
|
||||
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
|
||||
@@ -62,7 +62,7 @@ for i in 1..(end + 1) {
|
||||
|
||||
- [`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.
|
||||
[^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.
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# Overflow
|
||||
|
||||
The factorial of a number grows quite fast.
|
||||
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.
|
||||
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**.
|
||||
> 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.
|
||||
>
|
||||
@@ -32,7 +32,7 @@ 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?
|
||||
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
|
||||
@@ -40,13 +40,13 @@ It boils down to two different approaches:
|
||||
|
||||
### Reject the operation
|
||||
|
||||
This is the most conservative approach: we stop the program when an integer overflow occurs.
|
||||
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**.
|
||||
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.
|
||||
|
||||
@@ -69,14 +69,14 @@ You may be wondering—what is a profile setting? Let's get into that!
|
||||
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`.
|
||||
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.
|
||||
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.
|
||||
> "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.
|
||||
@@ -90,12 +90,12 @@ 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.
|
||||
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.
|
||||
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.
|
||||
@@ -107,4 +107,4 @@ application, you can run benchmarks to decide if it's something you can afford.
|
||||
## 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.
|
||||
for an in-depth discussion about integer overflow in Rust.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Case-by-case behavior
|
||||
|
||||
`overflow-checks` is a blunt tool: it's a global setting that affects the whole program.
|
||||
`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].
|
||||
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
|
||||
@@ -18,7 +18,7 @@ assert_eq!(sum, 0);
|
||||
|
||||
## `saturating_` methods
|
||||
|
||||
Alternatively, you can opt into **saturating arithmetic** by using the `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:
|
||||
|
||||
@@ -29,7 +29,7 @@ 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).
|
||||
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
|
||||
@@ -40,4 +40,4 @@ when performing the arithmetic operation.
|
||||
- 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.
|
||||
We'll cover methods (and how to define them) in the next chapter.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Conversions, pt. 1
|
||||
|
||||
We've repeated over and over again that Rust won't perform
|
||||
implicit type conversions for integers.
|
||||
implicit type conversions for integers.\
|
||||
How do you perform _explicit_ conversions then?
|
||||
|
||||
## `as`
|
||||
|
||||
You can use the `as` operator to convert between integer types.
|
||||
You can use the `as` operator to convert between integer types.\
|
||||
`as` conversions are **infallible**.
|
||||
|
||||
For example:
|
||||
@@ -24,7 +24,7 @@ let c: u64 = a as _;
|
||||
```
|
||||
|
||||
The semantics of this conversion are what you expect: all `u32` values are valid `u64`
|
||||
values.
|
||||
values.
|
||||
|
||||
### Truncation
|
||||
|
||||
@@ -38,11 +38,11 @@ let b = a as u8;
|
||||
```
|
||||
|
||||
This program will run without issues, because `as` conversions are infallible.
|
||||
But what is the value of `b`?
|
||||
But what is the value of `b`?
|
||||
When going from a larger integer type to a smaller, the Rust compiler will perform
|
||||
a **truncation**.
|
||||
a **truncation**.
|
||||
|
||||
To understand what happens, let's start by looking at how `256u16` is
|
||||
To understand what happens, let's start by looking at how `256u16` is
|
||||
represented in memory, as a sequence of bits:
|
||||
|
||||
```text
|
||||
@@ -59,10 +59,10 @@ memory representation:
|
||||
0 0 0 0 0 0 0 0
|
||||
| |
|
||||
+---------------+
|
||||
Last 8 bits
|
||||
Last 8 bits
|
||||
```
|
||||
|
||||
Hence `256 as u8` is equal to `0`. That's... not ideal, in most scenarios.
|
||||
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:
|
||||
|
||||
@@ -79,19 +79,19 @@ error: literal out of range for `i8`
|
||||
|
||||
### 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.md) that we'll
|
||||
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.md) that we'll
|
||||
explore later in the course.
|
||||
|
||||
### Limitations
|
||||
|
||||
Surprising behaviour is not the only downside of `as` casting.
|
||||
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.md)
|
||||
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.md)
|
||||
and [infallible](../04_traits/09_from.md)), which we'll explore later on.
|
||||
|
||||
## References
|
||||
@@ -100,6 +100,6 @@ and [infallible](../04_traits/09_from.md)), which we'll explore later on.
|
||||
|
||||
## 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.
|
||||
- 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