Formatter (#51)

Enforce consistent formatting use `dprint`
This commit is contained in:
Luca Palmieri
2024-05-24 17:00:03 +02:00
committed by GitHub
parent 537118574b
commit 99591a715e
157 changed files with 1057 additions and 1044 deletions

View File

@@ -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`

View File

@@ -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`

View File

@@ -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!

View File

@@ -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`

View File

@@ -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`

View File

@@ -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).

View File

@@ -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).

View File

@@ -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:

View File

@@ -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).

View File

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

View File

@@ -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

View File

@@ -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).

View File

@@ -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