100 exercises to learn Rust

This commit is contained in:
LukeMathWalker
2024-05-12 22:21:03 +02:00
commit 5edebf6cf2
309 changed files with 13173 additions and 0 deletions

View 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.