@@ -1,14 +1,14 @@
|
||||
# Modelling A Ticket
|
||||
|
||||
The first chapter should have given you a good grasp over some of Rust's primitive types, operators and
|
||||
basic control flow constructs.
|
||||
In this chapter we'll go one step further and cover what makes Rust truly unique: **ownership**.
|
||||
The first chapter should have given you a good grasp over some of Rust's primitive types, operators and
|
||||
basic control flow constructs.\
|
||||
In this chapter we'll go one step further and cover what makes Rust truly unique: **ownership**.\
|
||||
Ownership is what enables Rust to be both memory-safe and performant, with no garbage collector.
|
||||
|
||||
As our running example, we'll use a (JIRA-like) ticket, the kind you'd use to track bugs, features, or tasks in
|
||||
a software project.
|
||||
We'll take a stab at modeling it in Rust. It'll be the first iteration—it won't be perfect nor very idiomatic
|
||||
by the end of the chapter. It'll be enough of a challenge though!
|
||||
a software project.\
|
||||
We'll take a stab at modeling it in Rust. It'll be the first iteration—it won't be perfect nor very idiomatic
|
||||
by the end of the chapter. It'll be enough of a challenge though!\
|
||||
To move forward you'll have to pick up several new Rust concepts, such as:
|
||||
|
||||
- `struct`s, one of Rust's ways to define custom types
|
||||
@@ -19,4 +19,4 @@ To move forward you'll have to pick up several new Rust concepts, such as:
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/03_ticket_v1/00_intro`
|
||||
- The exercise for this section is located in `exercises/03_ticket_v1/00_intro`
|
||||
|
||||
@@ -6,9 +6,9 @@ We need to keep track of three pieces of information for each ticket:
|
||||
- A description
|
||||
- A status
|
||||
|
||||
We can start by using a [`String`](https://doc.rust-lang.org/std/string/struct.String.html)
|
||||
to represent them. `String` is the type defined in Rust's standard library to represent
|
||||
[UTF-8 encoded](https://en.wikipedia.org/wiki/UTF-8) text.
|
||||
We can start by using a [`String`](https://doc.rust-lang.org/std/string/struct.String.html)
|
||||
to represent them. `String` is the type defined in Rust's standard library to represent
|
||||
[UTF-8 encoded](https://en.wikipedia.org/wiki/UTF-8) text.
|
||||
|
||||
But how do we **combine** these three pieces of information into a single entity?
|
||||
|
||||
@@ -28,7 +28,7 @@ A struct is quite similar to what you would call a class or an object in other p
|
||||
|
||||
## Defining fields
|
||||
|
||||
The new type is built by combining other types as **fields**.
|
||||
The new type is built by combining other types as **fields**.\
|
||||
Each field must have a name and a type, separated by a colon, `:`. If there are multiple fields, they are separated by a comma, `,`.
|
||||
|
||||
Fields don't have to be of the same type, as you can see in the `Configuration` struct below:
|
||||
@@ -64,7 +64,7 @@ let x = ticket.description;
|
||||
|
||||
## Methods
|
||||
|
||||
We can attach behaviour to our structs by defining **methods**.
|
||||
We can attach behaviour to our structs by defining **methods**.\
|
||||
Using the `Ticket` struct as an example:
|
||||
|
||||
```rust
|
||||
@@ -140,4 +140,3 @@ but it's definitely more verbose. Prefer the method call syntax when possible.
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/03_ticket_v1/01_struct`
|
||||
|
||||
@@ -10,9 +10,9 @@ struct Ticket {
|
||||
}
|
||||
```
|
||||
|
||||
We are using "raw" types for the fields of our `Ticket` struct.
|
||||
This means that users can create a ticket with an empty title, a suuuuuuuper long description or
|
||||
a nonsensical status (e.g. "Funny").
|
||||
We are using "raw" types for the fields of our `Ticket` struct.
|
||||
This means that users can create a ticket with an empty title, a suuuuuuuper long description or
|
||||
a nonsensical status (e.g. "Funny").\
|
||||
We can do better than that!
|
||||
|
||||
## References
|
||||
@@ -21,5 +21,5 @@ We can do better than that!
|
||||
|
||||
## Further reading
|
||||
|
||||
- Check out [`String`'s documentation](https://doc.rust-lang.org/std/string/struct.String.html)
|
||||
for a thorough overview of the methods it provides. You'll need it for the exercise!
|
||||
- Check out [`String`'s documentation](https://doc.rust-lang.org/std/string/struct.String.html)
|
||||
for a thorough overview of the methods it provides. You'll need it for the exercise!
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Modules
|
||||
# Modules
|
||||
|
||||
The `new` method you've just defined is trying to enforce some **constraints** on the field values for `Ticket`.
|
||||
But are those invariants really enforced? What prevents a developer from creating a `Ticket`
|
||||
@@ -9,8 +9,8 @@ Let's start with modules.
|
||||
|
||||
## What is a module?
|
||||
|
||||
In Rust a **module** is a way to group related code together, under a common namespace (i.e. the module's name).
|
||||
You've already seen modules in action: the unit tests that verify the correctness of your code are defined in a
|
||||
In Rust a **module** is a way to group related code together, under a common namespace (i.e. the module's name).\
|
||||
You've already seen modules in action: the unit tests that verify the correctness of your code are defined in a
|
||||
different module, named `tests`.
|
||||
|
||||
```rust
|
||||
@@ -23,11 +23,11 @@ mod tests {
|
||||
## Inline modules
|
||||
|
||||
The `tests` module above is an example of an **inline module**: the module declaration (`mod tests`) and the module
|
||||
contents (the stuff inside `{ ... }`) are next to each other.
|
||||
contents (the stuff inside `{ ... }`) are next to each other.
|
||||
|
||||
## Module tree
|
||||
|
||||
Modules can be nested, forming a **tree** structure.
|
||||
Modules can be nested, forming a **tree** structure.\
|
||||
The root of the tree is the **crate** itself, which is the top-level module that contains all the other modules.
|
||||
For a library crate, the root module is usually `src/lib.rs` (unless its location has been customized).
|
||||
The root module is also known as the **crate root**.
|
||||
@@ -43,9 +43,9 @@ multiple files. In the parent module, you declare the existence of a submodule u
|
||||
mod dog;
|
||||
```
|
||||
|
||||
`cargo`, Rust's build tool, is then in charge of finding the file that contains
|
||||
the module implementation.
|
||||
If your module is declared in the root of your crate (e.g. `src/lib.rs` or `src/main.rs`),
|
||||
`cargo`, Rust's build tool, is then in charge of finding the file that contains
|
||||
the module implementation.\
|
||||
If your module is declared in the root of your crate (e.g. `src/lib.rs` or `src/main.rs`),
|
||||
`cargo` expects the file to be named either:
|
||||
|
||||
- `src/<module_name>.rs`
|
||||
@@ -76,7 +76,7 @@ fn mark_ticket_as_done(ticket: Ticket) {
|
||||
}
|
||||
```
|
||||
|
||||
That's not the case if you want to access an entity from a different module.
|
||||
That's not the case if you want to access an entity from a different module.\
|
||||
You have to use a **path** pointing to the entity you want to access.
|
||||
|
||||
You can compose the path in various ways:
|
||||
@@ -106,13 +106,13 @@ You can also import all the items from a module with a single `use` statement.
|
||||
use crate::module_1::module_2::*;
|
||||
```
|
||||
|
||||
This is known as a **star import**.
|
||||
This is known as a **star import**.\
|
||||
It is generally discouraged because it can pollute the current namespace, making it hard to understand
|
||||
where each name comes from and potentially introducing name conflicts.
|
||||
where each name comes from and potentially introducing name conflicts.\
|
||||
Nonetheless, it can be useful in some cases, like when writing unit tests. You might have noticed
|
||||
that most of our test modules start with a `use super::*;` statement to bring all the items from the parent module
|
||||
(the one being tested) into scope.
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/03_ticket_v1/03_modules`
|
||||
- The exercise for this section is located in `exercises/03_ticket_v1/03_modules`
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Visibility
|
||||
|
||||
When you start breaking down your code into multiple modules, you need to start thinking about **visibility**.
|
||||
When you start breaking down your code into multiple modules, you need to start thinking about **visibility**.
|
||||
Visibility determines which regions of your code (or other people's code) can access a given entity,
|
||||
be it a struct, a function, a field, etc.
|
||||
|
||||
## Private by default
|
||||
|
||||
By default, everything in Rust is **private**.
|
||||
By default, everything in Rust is **private**.\
|
||||
A private entity can only be accessed:
|
||||
|
||||
1. within the same module where it's defined, or
|
||||
@@ -16,16 +16,16 @@ We've used this extensively in the previous exercises:
|
||||
|
||||
- `create_todo_ticket` worked (once you added a `use` statement) because `helpers` is a submodule of the crate root,
|
||||
where `Ticket` is defined. Therefore, `create_todo_ticket` can access `Ticket` without any issues even
|
||||
though `Ticket` is private.
|
||||
- All our unit tests are defined in a submodule of the code they're testing, so they can access everything without
|
||||
restrictions.
|
||||
though `Ticket` is private.
|
||||
- All our unit tests are defined in a submodule of the code they're testing, so they can access everything without
|
||||
restrictions.
|
||||
|
||||
## Visibility modifiers
|
||||
|
||||
You can modify the default visibility of an entity using a **visibility modifier**.
|
||||
You can modify the default visibility of an entity using a **visibility modifier**.\
|
||||
Some common visibility modifiers are:
|
||||
|
||||
- `pub`: makes the entity **public**, i.e. accessible from outside the module where it's defined, potentially from
|
||||
- `pub`: makes the entity **public**, i.e. accessible from outside the module where it's defined, potentially from
|
||||
other crates.
|
||||
- `pub(crate)`: makes the entity public within the same **crate**, but not outside of it.
|
||||
- `pub(super)`: makes the entity public within the parent module.
|
||||
@@ -46,4 +46,4 @@ The `active` field, instead, is private and can only be accessed from within the
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/03_ticket_v1/04_visibility`
|
||||
- The exercise for this section is located in `exercises/03_ticket_v1/04_visibility`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Encapsulation
|
||||
|
||||
Now that we have a basic understanding of modules and visibility, let's circle back to **encapsulation**.
|
||||
Now that we have a basic understanding of modules and visibility, let's circle back to **encapsulation**.\
|
||||
Encapsulation is the practice of hiding the internal representation of an object. It is most commonly
|
||||
used to enforce some **invariants** on the object's state.
|
||||
|
||||
@@ -14,16 +14,16 @@ struct Ticket {
|
||||
}
|
||||
```
|
||||
|
||||
If all fields are made public, there is no encapsulation.
|
||||
If all fields are made public, there is no encapsulation.\
|
||||
You must assume that the fields can be modified at any time, set to any value that's allowed by
|
||||
their type. You can't rule out that a ticket might have an empty title or a status
|
||||
their type. You can't rule out that a ticket might have an empty title or a status
|
||||
that doesn't make sense.
|
||||
|
||||
To enforce stricter rules, we must keep the fields private[^newtype].
|
||||
We can then provide public methods to interact with a `Ticket` instance.
|
||||
To enforce stricter rules, we must keep the fields private[^newtype].
|
||||
We can then provide public methods to interact with a `Ticket` instance.
|
||||
Those public methods will have the responsibility of upholding our invariants (e.g. a title must not be empty).
|
||||
|
||||
If all fields are private, it is no longer possible to create a `Ticket` instance directly using the struct
|
||||
If all fields are private, it is no longer possible to create a `Ticket` instance directly using the struct
|
||||
instantiation syntax:
|
||||
|
||||
```rust
|
||||
@@ -35,9 +35,9 @@ let ticket = Ticket {
|
||||
};
|
||||
```
|
||||
|
||||
You've seen this in action in the previous exercise on visibility.
|
||||
You've seen this in action in the previous exercise on visibility.\
|
||||
We now need to provide one or more public **constructors**—i.e. static methods or functions that can be used
|
||||
from outside the module to create a new instance of the struct.
|
||||
from outside the module to create a new instance of the struct.\
|
||||
Luckily enough we already have one: `Ticket::new`, as implemented in [a previous exercise](02_validation.md).
|
||||
|
||||
## Accessor methods
|
||||
@@ -47,10 +47,10 @@ In summary:
|
||||
- All `Ticket` fields are private
|
||||
- We provide a public constructor, `Ticket::new`, that enforces our validation rules on creation
|
||||
|
||||
That's a good start, but it's not enough: apart from creating a `Ticket`, we also need to interact with it.
|
||||
That's a good start, but it's not enough: apart from creating a `Ticket`, we also need to interact with it.
|
||||
But how can we access the fields if they're private?
|
||||
|
||||
We need to provide **accessor methods**.
|
||||
We need to provide **accessor methods**.\
|
||||
Accessor methods are public methods that allow you to read the value of a private field (or fields) of a struct.
|
||||
|
||||
Rust doesn't have a built-in way to generate accessor methods for you, like some other languages do.
|
||||
@@ -60,4 +60,4 @@ You have to write them yourself—they're just regular methods.
|
||||
|
||||
- The exercise for this section is located in `exercises/03_ticket_v1/05_encapsulation`
|
||||
|
||||
[^newtype]: Or refine their type, a technique we'll explore [later on](../05_ticket_v2/15_outro.md).
|
||||
[^newtype]: Or refine their type, a technique we'll explore [later on](../05_ticket_v2/15_outro.md).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Ownership
|
||||
|
||||
If you solved the previous exercise using what this course has taught you so far,
|
||||
If you solved the previous exercise using what this course has taught you so far,
|
||||
your accessor methods probably look like this:
|
||||
|
||||
```rust
|
||||
@@ -74,11 +74,11 @@ All these things are true at the same time for Rust:
|
||||
2. As a developer, you rarely have to manage memory directly
|
||||
3. You can't cause dangling pointers, double frees, and other memory-related bugs
|
||||
|
||||
Languages like Python, JavaScript, and Java give you 2. and 3., but not 1.
|
||||
Language like C or C++ give you 1., but neither 2. nor 3.
|
||||
Languages like Python, JavaScript, and Java give you 2. and 3., but not 1.\
|
||||
Language like C or C++ give you 1., but neither 2. nor 3.
|
||||
|
||||
Depending on your background, 3. might sound a bit arcane: what is a "dangling pointer"?
|
||||
What is a "double free"? Why are they dangerous?
|
||||
Depending on your background, 3. might sound a bit arcane: what is a "dangling pointer"?
|
||||
What is a "double free"? Why are they dangerous?\
|
||||
Don't worry: we'll cover these concepts in more details during the rest of the course.
|
||||
|
||||
For now, though, let's focus on learning how to work within Rust's ownership system.
|
||||
@@ -89,8 +89,8 @@ In Rust, each value has an **owner**, statically determined at compile-time.
|
||||
There is only one owner for each value at any given time.
|
||||
|
||||
## Move semantics
|
||||
|
||||
Ownership can be transferred.
|
||||
|
||||
Ownership can be transferred.
|
||||
|
||||
If you own a value, for example, you can transfer ownership to another variable:
|
||||
|
||||
@@ -113,9 +113,9 @@ impl Ticket {
|
||||
}
|
||||
```
|
||||
|
||||
`Ticket::description` takes ownership of the `Ticket` instance it's called on.
|
||||
`Ticket::description` takes ownership of the `Ticket` instance it's called on.\
|
||||
This is known as **move semantics**: ownership of the value (`self`) is **moved** from the caller to
|
||||
the callee, and the caller can't use it anymore.
|
||||
the callee, and the caller can't use it anymore.
|
||||
|
||||
That's exactly the language used by the compiler in the error message we saw earlier:
|
||||
|
||||
@@ -152,10 +152,10 @@ To build _useful_ accessor methods we need to start working with **references**.
|
||||
|
||||
## Borrowing
|
||||
|
||||
It is desirable to have methods that can read the value of a variable without taking ownership of it.
|
||||
It is desirable to have methods that can read the value of a variable without taking ownership of it.\
|
||||
Programming would be quite limited otherwise. In Rust, that's done via **borrowing**.
|
||||
|
||||
Whenever you borrow a value, you get a **reference** to it.
|
||||
Whenever you borrow a value, you get a **reference** to it.\
|
||||
References are tagged with their privileges[^refine]:
|
||||
|
||||
- Immutable references (`&`) allow you to read the value, but not to mutate it
|
||||
@@ -173,20 +173,20 @@ To ensure these two properties, Rust has to introduce some restrictions on refer
|
||||
- The owner can't mutate the value while it's being borrowed
|
||||
- You can have as many immutable references as you want, as long as there are no mutable references
|
||||
|
||||
In a way, you can think of an immutable reference as a "read-only" lock on the value,
|
||||
while a mutable reference is like a "read-write" lock.
|
||||
In a way, you can think of an immutable reference as a "read-only" lock on the value,
|
||||
while a mutable reference is like a "read-write" lock.
|
||||
|
||||
All these restrictions are enforced at compile-time by the borrow checker.
|
||||
|
||||
### Syntax
|
||||
|
||||
How do you borrow a value, in practice?
|
||||
How do you borrow a value, in practice?\
|
||||
By adding `&` or `&mut` **in front a variable**, you're borrowing its value.
|
||||
Careful though! The same symbols (`&` and `&mut`) in **front of a type** have a different meaning:
|
||||
Careful though! The same symbols (`&` and `&mut`) in **front of a type** have a different meaning:
|
||||
they denote a different type, a reference to the original type.
|
||||
|
||||
For example:
|
||||
|
||||
|
||||
```rust
|
||||
struct Configuration {
|
||||
version: u32,
|
||||
@@ -220,18 +220,18 @@ fn f(number: &mut u32) -> &u32 {
|
||||
|
||||
## Breathe in, breathe out
|
||||
|
||||
Rust's ownership system can be a bit overwhelming at first.
|
||||
But don't worry: it'll become second nature with practice.
|
||||
Rust's ownership system can be a bit overwhelming at first.\
|
||||
But don't worry: it'll become second nature with practice.\
|
||||
And you're going to get a lot of practice over the rest of this chapter, as well as the rest of the course!
|
||||
We'll revisit each concept multiple times to make sure you get familiar with them
|
||||
and truly understand how they work.
|
||||
|
||||
Towards the end of this chapter we'll explain *why* Rust's ownership system is designed the way it is.
|
||||
For the time being, focus on understanding the *how*. Take each compiler error as a learning opportunity!
|
||||
Towards the end of this chapter we'll explain _why_ Rust's ownership system is designed the way it is.
|
||||
For the time being, focus on understanding the _how_. Take each compiler error as a learning opportunity!
|
||||
|
||||
## References
|
||||
|
||||
- The exercise for this section is located in `exercises/03_ticket_v1/06_ownership`
|
||||
|
||||
[^refine]: This is a great mental model to start out, but it doesn't capture the _full_ picture.
|
||||
We'll refine our understanding of references [later in the course](../07_threads/06_interior_mutability.md).
|
||||
We'll refine our understanding of references [later in the course](../07_threads/06_interior_mutability.md).
|
||||
|
||||
@@ -18,7 +18,7 @@ impl Ticket {
|
||||
}
|
||||
```
|
||||
|
||||
A sprinkle of `&` here and there did the trick!
|
||||
A sprinkle of `&` here and there did the trick!\
|
||||
We now have a way to access the fields of a `Ticket` instance without consuming it in the process.
|
||||
Let's see how we can enhance our `Ticket` struct with **setter methods** next.
|
||||
|
||||
@@ -46,7 +46,7 @@ impl Ticket {
|
||||
}
|
||||
```
|
||||
|
||||
It takes ownership of `self`, changes the title, and returns the modified `Ticket` instance.
|
||||
It takes ownership of `self`, changes the title, and returns the modified `Ticket` instance.\
|
||||
This is how you'd use it:
|
||||
|
||||
```rust
|
||||
@@ -55,8 +55,8 @@ let ticket = ticket.set_title("New title".into());
|
||||
```
|
||||
|
||||
Since `set_title` takes ownership of `self` (i.e. it **consumes it**), we need to reassign the result to a variable.
|
||||
In the example above we take advantage of **variable shadowing** to reuse the same variable name: when
|
||||
you declare a new variable with the same name as an existing one, the new variable **shadows** the old one. This
|
||||
In the example above we take advantage of **variable shadowing** to reuse the same variable name: when
|
||||
you declare a new variable with the same name as an existing one, the new variable **shadows** the old one. This
|
||||
is a common pattern in Rust code.
|
||||
|
||||
`self`-setters work quite nicely when you need to change multiple fields at once: you can chain multiple calls together!
|
||||
@@ -82,8 +82,8 @@ impl Ticket {
|
||||
}
|
||||
```
|
||||
|
||||
This time the method takes a mutable reference to `self` as input, changes the title, and that's it.
|
||||
Nothing is returned.
|
||||
This time the method takes a mutable reference to `self` as input, changes the title, and that's it.
|
||||
Nothing is returned.
|
||||
|
||||
You'd use it like this:
|
||||
|
||||
|
||||
@@ -5,16 +5,16 @@ Now it's a good time to take a look under the hood: let's talk about **memory**.
|
||||
|
||||
## Stack and heap
|
||||
|
||||
When discussing memory, you'll often hear people talk about the **stack** and the **heap**.
|
||||
When discussing memory, you'll often hear people talk about the **stack** and the **heap**.\
|
||||
These are two different memory regions used by programs to store data.
|
||||
|
||||
Let's start with the stack.
|
||||
|
||||
## Stack
|
||||
|
||||
The **stack** is a **LIFO** (Last In, First Out) data structure.
|
||||
The **stack** is a **LIFO** (Last In, First Out) data structure.\
|
||||
When you call a function, a new **stack frame** is added on top of the stack. That stack frame stores
|
||||
the function's arguments, local variables and a few "bookkeeping" values.
|
||||
the function's arguments, local variables and a few "bookkeeping" values.\
|
||||
When the function returns, the stack frame is popped off the stack[^stack-overflow].
|
||||
|
||||
```text
|
||||
@@ -25,22 +25,22 @@ When the function returns, the stack frame is popped off the stack[^stack-overfl
|
||||
+-----------------+ +-----------------+ +-----------------+
|
||||
```
|
||||
|
||||
From an operational point of view, stack allocation/de-allocation is **very fast**.
|
||||
From an operational point of view, stack allocation/de-allocation is **very fast**.\
|
||||
We are always pushing and popping data from the top of the stack, so we don't need to search for free memory.
|
||||
We also don't have to worry about fragmentation: the stack is a single contiguous block of memory.
|
||||
|
||||
### Rust
|
||||
|
||||
Rust will often allocate data on the stack.
|
||||
You have a `u32` input argument in a function? Those 32 bits will be on the stack.
|
||||
You define a local variable of type `i64`? Those 64 bits will be on the stack.
|
||||
Rust will often allocate data on the stack.\
|
||||
You have a `u32` input argument in a function? Those 32 bits will be on the stack.\
|
||||
You define a local variable of type `i64`? Those 64 bits will be on the stack.\
|
||||
It all works quite nicely because the size of those integers is known at compile time, therefore
|
||||
the compiled program knows how much space it needs to reserve on the stack for them.
|
||||
|
||||
### `std::mem::size_of`
|
||||
|
||||
You can verify how much space a type would take on the stack
|
||||
using the [`std::mem::size_of`](https://doc.rust-lang.org/std/mem/fn.size_of.html) function.
|
||||
You can verify how much space a type would take on the stack
|
||||
using the [`std::mem::size_of`](https://doc.rust-lang.org/std/mem/fn.size_of.html) function.
|
||||
|
||||
For a `u8`, for example:
|
||||
|
||||
@@ -57,6 +57,6 @@ assert_eq!(std::mem::size_of::<u8>(), 1);
|
||||
- The exercise for this section is located in `exercises/03_ticket_v1/08_stack`
|
||||
|
||||
[^stack-overflow]: If you have nested function calls, each function pushes its data onto the stack when it's called but
|
||||
it doesn't pop it off until the innermost function returns.
|
||||
If you have too many nested function calls, you can run out of stack space—the stack is not infinite!
|
||||
That's called a [**stack overflow**](https://en.wikipedia.org/wiki/Stack_overflow).
|
||||
it doesn't pop it off until the innermost function returns.
|
||||
If you have too many nested function calls, you can run out of stack space—the stack is not infinite!
|
||||
That's called a [**stack overflow**](https://en.wikipedia.org/wiki/Stack_overflow).
|
||||
|
||||
@@ -6,14 +6,14 @@ That's where the **heap** comes in.
|
||||
|
||||
## Heap allocations
|
||||
|
||||
You can visualize the heap as a big chunk of memory—a huge array, if you will.
|
||||
You can visualize the heap as a big chunk of memory—a huge array, if you will.\
|
||||
Whenever you need to store data on the heap, you ask a special program, the **allocator**, to reserve for you
|
||||
a subset of the heap. We call this interaction (and the memory you reserved) a **heap allocation**.
|
||||
If the allocation succeeds, the allocator will give you a **pointer** to the start of the reserved block.
|
||||
|
||||
## No automatic de-allocation
|
||||
|
||||
The heap is structured quite differently from the stack.
|
||||
The heap is structured quite differently from the stack.\
|
||||
Heap allocations are not contiguous, they can be located anywhere inside the heap.
|
||||
|
||||
```
|
||||
@@ -29,15 +29,15 @@ calling the allocator again to **free** the memory you no longer need.
|
||||
## Performance
|
||||
|
||||
The heap's flexibility comes at a cost: heap allocations are **slower** than stack allocations.
|
||||
There's a lot more bookkeeping involved!
|
||||
If you read articles about performance optimization you'll often be advised to minimize heap allocations
|
||||
There's a lot more bookkeeping involved!\
|
||||
If you read articles about performance optimization you'll often be advised to minimize heap allocations
|
||||
and prefer stack-allocated data whenever possible.
|
||||
|
||||
## `String`'s memory layout
|
||||
|
||||
When you create a local variable of type `String`,
|
||||
Rust is forced to allocate on the heap[^empty]: it doesn't know in advance how much text you're going to put in it,
|
||||
so it can't reserve the right amount of space on the stack.
|
||||
When you create a local variable of type `String`,
|
||||
Rust is forced to allocate on the heap[^empty]: it doesn't know in advance how much text you're going to put in it,
|
||||
so it can't reserve the right amount of space on the stack.\
|
||||
But a `String` is not _entirely_ heap-allocated, it also keeps some data on the stack. In particular:
|
||||
|
||||
- The **pointer** to the heap region you reserved.
|
||||
@@ -65,11 +65,11 @@ Heap: | ? | ? | ? | ? | ? |
|
||||
+---+---+---+---+---+
|
||||
```
|
||||
|
||||
We asked for a `String` that can hold up to 5 bytes of text.
|
||||
`String::with_capacity` goes to the allocator and asks for 5 bytes of heap memory. The allocator returns
|
||||
a pointer to the start of that memory block.
|
||||
The `String` is empty, though. On the stack, we keep track of this information by distinguishing between
|
||||
the length and the capacity: this `String` can hold up to 5 bytes, but it currently holds 0 bytes of
|
||||
We asked for a `String` that can hold up to 5 bytes of text.\
|
||||
`String::with_capacity` goes to the allocator and asks for 5 bytes of heap memory. The allocator returns
|
||||
a pointer to the start of that memory block.\
|
||||
The `String` is empty, though. On the stack, we keep track of this information by distinguishing between
|
||||
the length and the capacity: this `String` can hold up to 5 bytes, but it currently holds 0 bytes of
|
||||
actual text.
|
||||
|
||||
If you push some text into the `String`, the situation will change:
|
||||
@@ -96,40 +96,40 @@ Three of the five bytes on the heap are used to store the characters `H`, `e`, a
|
||||
|
||||
### `usize`
|
||||
|
||||
How much space do we need to store pointer, length and capacity on the stack?
|
||||
How much space do we need to store pointer, length and capacity on the stack?\
|
||||
It depends on the **architecture** of the machine you're running on.
|
||||
|
||||
Every memory location on your machine has an [**address**](https://en.wikipedia.org/wiki/Memory_address), commonly
|
||||
represented as an unsigned integer.
|
||||
Depending on the maximum size of the address space (i.e. how much memory your machine can address),
|
||||
this integer can have a different size. Most modern machines use either a 32-bit or a 64-bit address space.
|
||||
Depending on the maximum size of the address space (i.e. how much memory your machine can address),
|
||||
this integer can have a different size. Most modern machines use either a 32-bit or a 64-bit address space.
|
||||
|
||||
Rust abstracts away these architecture-specific details by providing the `usize` type:
|
||||
an unsigned integer that's as big as the number of bytes needed to address memory on your machine.
|
||||
On a 32-bit machine, `usize` is equivalent to `u32`. On a 64-bit machine, it matches `u64`.
|
||||
|
||||
Capacity, length and pointers are all represented as `usize`s in Rust[^equivalence].
|
||||
Capacity, length and pointers are all represented as `usize`s in Rust[^equivalence].
|
||||
|
||||
### No `std::mem::size_of` for the heap
|
||||
|
||||
`std::mem::size_of` returns the amount of space a type would take on the stack,
|
||||
which is also known as the **size of the type**.
|
||||
`std::mem::size_of` returns the amount of space a type would take on the stack,
|
||||
which is also known as the **size of the type**.
|
||||
|
||||
> What about the memory buffer that `String` is managing on the heap? Isn't that
|
||||
> part of the size of `String`?
|
||||
|
||||
No!
|
||||
That heap allocation is a **resource** that `String` is managing.
|
||||
It's not considered to be part of the `String` type by the compiler.
|
||||
No!\
|
||||
That heap allocation is a **resource** that `String` is managing.
|
||||
It's not considered to be part of the `String` type by the compiler.
|
||||
|
||||
`std::mem::size_of` doesn't know (or care) about additional heap-allocated data
|
||||
`std::mem::size_of` doesn't know (or care) about additional heap-allocated data
|
||||
that a type might manage or refer to via pointers, as is the case with `String`,
|
||||
therefore it doesn't track its size.
|
||||
|
||||
Unfortunately there is no equivalent of `std::mem::size_of` to measure the amount of
|
||||
heap memory that a certain value is allocating at runtime. Some types might
|
||||
provide methods to inspect their heap usage (e.g. `String`'s `capacity` method),
|
||||
but there is no general-purpose "API" to retrieve runtime heap usage in Rust.
|
||||
but there is no general-purpose "API" to retrieve runtime heap usage in Rust.\
|
||||
You can, however, use a memory profiler tool (e.g. [DHAT](https://valgrind.org/docs/manual/dh-manual.html)
|
||||
or [a custom allocator](https://docs.rs/dhat/latest/dhat/)) to inspect the heap usage of your program.
|
||||
|
||||
@@ -138,9 +138,9 @@ or [a custom allocator](https://docs.rs/dhat/latest/dhat/)) to inspect the heap
|
||||
- The exercise for this section is located in `exercises/03_ticket_v1/09_heap`
|
||||
|
||||
[^empty]: `std` doesn't allocate if you create an **empty** `String` (i.e. `String::new()`).
|
||||
Heap memory will be reserved when you push data into it for the first time.
|
||||
Heap memory will be reserved when you push data into it for the first time.
|
||||
|
||||
[^equivalence]: The size of a pointer depends on the operating system too.
|
||||
In certain environments, a pointer is **larger** than a memory address (e.g. [CHERI](https://blog.acolyer.org/2019/05/28/cheri-abi/)).
|
||||
Rust makes the simplifying assumption that pointers are the same size as memory addresses,
|
||||
which is true for most modern systems you're likely to encounter.
|
||||
In certain environments, a pointer is **larger** than a memory address (e.g. [CHERI](https://blog.acolyer.org/2019/05/28/cheri-abi/)).
|
||||
Rust makes the simplifying assumption that pointers are the same size as memory addresses,
|
||||
which is true for most modern systems you're likely to encounter.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
What about references, like `&String` or `&mut String`? How are they represented in memory?
|
||||
|
||||
Most references[^fat] in Rust are represented, in memory, as a pointer to a memory location.
|
||||
Most references[^fat] in Rust are represented, in memory, as a pointer to a memory location.\
|
||||
It follows that their size is the same as the size of a pointer, a `usize`.
|
||||
|
||||
You can verify this using `std::mem::size_of`:
|
||||
@@ -12,7 +12,7 @@ assert_eq!(std::mem::size_of::<&String>(), 8);
|
||||
assert_eq!(std::mem::size_of::<&mut String>(), 8);
|
||||
```
|
||||
|
||||
A `&String`, in particular, is a pointer to the memory location where the `String`'s metadata is stored.
|
||||
A `&String`, in particular, is a pointer to the memory location where the `String`'s metadata is stored.\
|
||||
If you run this snippet:
|
||||
|
||||
```rust
|
||||
@@ -38,11 +38,11 @@ Heap | H | e | y | ? | ? |
|
||||
```
|
||||
|
||||
It's a pointer to a pointer to the heap-allocated data, if you will.
|
||||
The same goes for `&mut String`.
|
||||
The same goes for `&mut String`.
|
||||
|
||||
## Not all pointers point to the heap
|
||||
|
||||
The example above should clarify one thing: not all pointers point to the heap.
|
||||
The example above should clarify one thing: not all pointers point to the heap.\
|
||||
They just point to a memory location, which _may_ be on the heap, but doesn't have to be.
|
||||
|
||||
## References
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Destructors
|
||||
|
||||
When introducing the heap, we mentioned that you're responsible for freeing the memory you allocate.
|
||||
When introducing the heap, we mentioned that you're responsible for freeing the memory you allocate.\
|
||||
When introducing the borrow-checker, we also stated that you rarely have to manage memory directly in Rust.
|
||||
|
||||
These two statements might seem contradictory at first.
|
||||
@@ -13,7 +13,7 @@ The **scope** of a variable is the region of Rust code where that variable is va
|
||||
The scope of a variable starts with its declaration.
|
||||
It ends when one of the following happens:
|
||||
|
||||
1. the block (i.e. the code between `{}`) where the variable was declared ends
|
||||
1. the block (i.e. the code between `{}`) where the variable was declared ends
|
||||
```rust
|
||||
fn main() {
|
||||
// `x` is not yet in scope here
|
||||
@@ -21,28 +21,28 @@ It ends when one of the following happens:
|
||||
let x = "World".to_string(); // <-- x's scope starts here...
|
||||
let h = "!".to_string(); // |
|
||||
} // <-------------- ...and ends here
|
||||
```
|
||||
```
|
||||
2. ownership of the variable is transferred to someone else (e.g. a function or another variable)
|
||||
```rust
|
||||
fn compute(t: String) {
|
||||
// Do something [...]
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
let s = "Hello".to_string(); // <-- s's scope starts here...
|
||||
// |
|
||||
compute(s); // <------------------- ..and ends here
|
||||
// because `s` is moved into `compute`
|
||||
}
|
||||
```
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Destructors
|
||||
|
||||
When the owner of a value goes out of scope, Rust invokes its **destructor**.
|
||||
When the owner of a value goes out of scope, Rust invokes its **destructor**.\
|
||||
The destructor tries to clean up the resources used by that value—in particular, whatever memory it allocated.
|
||||
|
||||
You can manually invoke the destructor of a value by passing it to `std::mem::drop`.
|
||||
That's why you'll often hear Rust developers saying "that value has been **dropped**" as a way to state that a value
|
||||
You can manually invoke the destructor of a value by passing it to `std::mem::drop`.\
|
||||
That's why you'll often hear Rust developers saying "that value has been **dropped**" as a way to state that a value
|
||||
has gone out of scope and its destructor has been invoked.
|
||||
|
||||
### Visualizing drop points
|
||||
@@ -55,7 +55,7 @@ fn main() {
|
||||
let x = "World".to_string();
|
||||
let h = "!".to_string();
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
It's equivalent to:
|
||||
|
||||
@@ -100,11 +100,11 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
Notice the difference: even though `s` is no longer valid after `compute` is called in `main`, there is no `drop(s)`
|
||||
Notice the difference: even though `s` is no longer valid after `compute` is called in `main`, there is no `drop(s)`
|
||||
in `main`.
|
||||
When you transfer ownership of a value to a function, you're also **transferring the responsibility of cleaning it up**.
|
||||
When you transfer ownership of a value to a function, you're also **transferring the responsibility of cleaning it up**.
|
||||
|
||||
This ensures that the destructor for a value is called **at most[^leak] once**, preventing
|
||||
This ensures that the destructor for a value is called **at most[^leak] once**, preventing
|
||||
[double free bugs](https://owasp.org/www-community/vulnerabilities/Doubly_freeing_memory) by design.
|
||||
|
||||
### Use after drop
|
||||
@@ -129,12 +129,12 @@ error[E0382]: use of moved value: `x`
|
||||
| ^ value used here after move
|
||||
```
|
||||
|
||||
Drop **consumes** the value it's called on, meaning that the value is no longer valid after the call.
|
||||
Drop **consumes** the value it's called on, meaning that the value is no longer valid after the call.\
|
||||
The compiler will therefore prevent you from using it, avoiding [use-after-free bugs](https://owasp.org/www-community/vulnerabilities/Using_freed_memory).
|
||||
|
||||
### Dropping references
|
||||
|
||||
What if a variable contains a reference?
|
||||
What if a variable contains a reference?\
|
||||
For example:
|
||||
|
||||
```rust
|
||||
@@ -143,7 +143,7 @@ let y = &x;
|
||||
drop(y);
|
||||
```
|
||||
|
||||
When you call `drop(y)`... nothing happens.
|
||||
When you call `drop(y)`... nothing happens.\
|
||||
If you actually try to compile this code, you'll get a warning:
|
||||
|
||||
```text
|
||||
@@ -158,7 +158,7 @@ warning: calls to `std::mem::drop` with a reference
|
||||
|
|
||||
```
|
||||
|
||||
It goes back to what we said earlier: we only want to call the destructor once.
|
||||
It goes back to what we said earlier: we only want to call the destructor once.\
|
||||
You can have multiple references to the same value—if we called the destructor for the value they point at
|
||||
when one of them goes out of scope, what would happen to the others?
|
||||
They would refer to a memory location that's no longer valid: a so-called [**dangling pointer**](https://en.wikipedia.org/wiki/Dangling_pointer),
|
||||
@@ -170,4 +170,4 @@ Rust's ownership system rules out these kinds of bugs by design.
|
||||
- The exercise for this section is located in `exercises/03_ticket_v1/11_destructor`
|
||||
|
||||
[^leak]: Rust doesn't guarantee that destructors will run. They won't, for example, if
|
||||
you explicitly choose to [leak memory](../07_threads/03_leak.md).
|
||||
you explicitly choose to [leak memory](../07_threads/03_leak.md).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Wrapping up
|
||||
|
||||
We've covered a lot of foundational Rust concepts in this chapter.
|
||||
Before moving on, let's go through one last exercise to consolidate what we've learned.
|
||||
We've covered a lot of foundational Rust concepts in this chapter.\
|
||||
Before moving on, let's go through one last exercise to consolidate what we've learned.
|
||||
You'll have minimal guidance this time—just the exercise description and the tests to guide you.
|
||||
|
||||
## References
|
||||
|
||||
Reference in New Issue
Block a user