Skip to content

Edit Chapter 9.01 #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 26, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 54 additions & 17 deletions 09_01_shared_references.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
# Shared References

In addition to a *mutable* reference, as indicated by the `&mut` keyword, we can also pass around a *shared* reference to a variable, which can be done by simply prefacing a variable with the `&` symbol. A mutable reference can modify the underlying data where as the shared reference cannot and is read-only. We already saw an example of a shared reference in chapter 6 where we created a slice reference to data on the heap by prepending `[u8]` with the `&` symbol.
In addition to a *mutable* reference, as indicated by the `&mut` keyword, we can also pass around a *shared* reference to a variable, which can be done by simply prefacing a variable with the `&` symbol.
A mutable reference can modify the underlying data where as the shared reference cannot and is read-only.
We already saw an example of a shared reference in chapter 6 where we created a slice reference to data on the heap by prepending `[u8]` with the `&` symbol.

A reference is a kind of pointer. It points to some data elsewhere and "borrows" it instead of "owns" it. What does this mean? Well let's see with an [example](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#references-and-borrowing):
A reference is a kind of pointer.
It points to some data elsewhere and "borrows" it instead of "owning" it.
What does this mean?
Well let's see with an [example](https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#references-and-borrowing):

```rust
fn main() {
Expand All @@ -18,7 +23,8 @@ fn calculate_length(s: String) -> usize {
}
```

You can run this code online with [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=cf786ab3bacd43e260e73ae5efa49d50). If you run it, you'll notice that there will be a compiler error:
You can run this code online with [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=cf786ab3bacd43e260e73ae5efa49d50).
If you run it, you'll notice that there will be a compiler error:
```console
Compiling playground v0.0.1 (/playground)
error[E0382]: borrow of moved value: `s1`
Expand All @@ -34,13 +40,25 @@ error[E0382]: borrow of moved value: `s1`
| ^^ value borrowed here after move
```

The error points out that a move occurs and that we are unable to "borrow" the value after a move. It also mentions that the type, `String` does not implement the `Copy` trait.
The error points out that a move occurs and that we are unable to "borrow" the value after a move.
It also mentions that the type, `String` does not implement the `Copy` trait.

By default, any type that does not implement the `Copy` trait will get "moved" when passed as an argument. This means the variable that contains it will no longer be available within the scope of original function. It's "ownership" is now passed to the function being called, which in this case is `calculate_length`. Why does Rust do this? Well this has to do with how Rust handles memory management internally. This way, it's always clear which variable "owns" data so that when that variable goes out of scope, the memory associated with it can be automatically freed. This is different from how other languages handle memory with garbage collection or reference counting to know when memory should be freed.
By default, any type that does not implement the `Copy` trait will get "moved" when passed as an argument.
This means the variable that contains it will no longer be available within the scope of original function.
It's "ownership" is now passed to the function being called, which in this case is `calculate_length`.

So if we want to keep referring to the string after it has been passed to the `calculate_length` function, we need to use a *reference*. The reference will "borrow" the value and won't actually "own" the underlying data. This means that when the reference goes out of scope and is no longer in use, the heap data and its owner will still remain.
Why does Rust do this?
Well this has to do with how Rust handles memory management internally.
This way, it's always clear which variable "owns" data so that when that variable goes out of scope, the memory associated with it can be automatically freed.
This is different from how other languages handle memory with garbage collection or reference counting to know when memory should be freed.

We can create a reference by placing the `&` symbol in front and modifying the function argument type:
Since `s1` was moved to the scope of `calculate_length`, the `String` data will be deallocated when the function returns.
That's why we can't print it when we are back in `main`, the data would not exist anymore.
If we want to keep referring to the string after it has been passed to the `calculate_length` function, we need to pass a *reference* as argument instead.
The reference will "borrow" the value and won't actually "own" the underlying data.
This means that when the reference goes out of scope and is no longer in use, the heap data and its owner will still remain.

We can create a reference by placing the `&` symbol in front of an identifier and modifying the function argument type:

```rust
fn main() {
Expand All @@ -61,7 +79,10 @@ This will work now and correctly print:
The length of 'hello' is 5.
```

`s1` remains the owner of the String. `s` in the `calculate_length` will merely borrow a shared, immutable reference to the String. This means that we wouldn't be able to mutate the String in `calculate_length`. For example this won't work,
`s1` remains the owner of the String.
`s` in the `calculate_length` will merely borrow a shared, immutable reference to the String.
This means that we wouldn't be able to mutate the String in `calculate_length`.
For example this won't work,

```rust
fn main() {
Expand Down Expand Up @@ -118,7 +139,8 @@ The length of 'hell' is 4.

That's the basics of shared and mutable references!

It's important to point out here that there is an exception to this rule, which we alluded to earlier and that is any type that implements the `Copy` trait, such as an integer type or an array `[u8; N]`. Typically, these are types that are stack allocated and don't require any heap allocations.
It's important to point out here that there is an exception to this rule, which we alluded to earlier and that is any type that implements the `Copy` trait, such as an integer type or an array `[u8; N]`.
Typically, these are types that are stack allocated and don't require any heap allocations.

Let's take a look at an example (see [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=bbc4ad315069566df4b5d853280492d1)):

Expand All @@ -135,10 +157,13 @@ fn calculate_len(mut arr: [u8; 4]) -> usize {
}
```

This will run just fine without needing to pass a shared reference. Why? Because a copy will automatically be made in the `calculate_len` function and Rust won't have to worry about or think about ownership of data on the heap.
This will run just fine without needing to pass a shared reference.
Why? Because a copy will automatically be made in the `calculate_len` function and Rust won't have to worry about or think about ownership of data on the heap.

### Single Writer or Multiple Readers
Rust enforces a simple, yet important rule when it comes to passing references and that is single writer OR multiple readers. In other words, you can have many different immutable, shared references to an object OR you can have just one mutable reference at any given time. You can't have both a shared reference and a mutable reference at the same time.
Rust enforces a simple, yet important rule when it comes to passing references and that is single writer OR multiple readers.
In other words, you can have many different immutable, shared references to an object OR you can have just one mutable reference at any given time.
You can't have both a shared reference and a mutable reference at the same time.

Let's walk through an example to see why:

Expand All @@ -150,9 +175,11 @@ fn main() {
r[0] // r still points to v, which doesn't point to anything and so is a dangling pointer
}
```
The statement `let aside = v` *moves* the vec from `v` into `aside`. However, `r` still references the variable `v` which is now uninitialized.
The statement `let aside = v` *moves* the vec from `v` into `aside`.
However, `r` still references the variable `v` which is now uninitialized.

If we have a shared reference to some variable and that variable goes out of scope or becomes uninitialized, we would end up with a dangling pointer. This can lead to unexpected behavior in a program and so Rust will attempt to avoid these possibilities.
If we have a shared reference to some variable and that variable goes out of scope or becomes uninitialized, we would end up with a dangling pointer.
This can lead to unexpected behavior in a program and so Rust will attempt to avoid these possibilities.

This is what the compiler will complain:
```console
Expand All @@ -168,14 +195,24 @@ error[E0505]: cannot move out of `v` because it is borrowed
17 | r[0]; // r still points to v, which doesn't point to anything and so is a dangling pointer
| - borrow later used here
```
In other words, Rust will complain and enforce the rule that we cannot make any changes to `v` while it is being borrowed as an immutable, shared reference. This will prevent a case of a dangling pointer.
In other words, Rust will complain and enforce the rule that we cannot make any changes to `v` while it is being borrowed as an immutable, shared reference.
This will prevent a case of a dangling pointer.

You may see this compiler error from time to time. Just remember the rule: you can only have a single writer (mutable reference) OR multiple readers (shared references).
You may see this compiler error from time to time.
Just remember the rule: you can only have a single writer (mutable reference) OR multiple readers (shared references).

Ok! That's it for references! Take a breather as you just got past one of the hardest aspects of understanding the Rust programmming language. Don't worry if it hasn't fully "sunk in" yet. This is something that takes time and practice to get familiar with. Just know that with time these concepts will make more sense and you might even begin to start appreciating them.
Ok! That's it for references!
Take a breather as you just got past one of the hardest aspects of understanding the Rust programmming language.
Don't worry if it hasn't fully "sunk in" yet.
This is something that takes time and practice to get familiar with.
Just know that with time these concepts will make more sense and you might even begin to start appreciating them.

### Quiz
*What do you think would happen if we attempted to modify the vector in our project while we have a slice that borrows a reference to it? Experiment by calling `.clear()` on the vector (after declaring it mutable). See example below. Run it and see what happens. Can you explain why the compiler is returning an error and the meaning of that error?*
*What do you think would happen if we attempted to modify the vector in our project while we have a slice that borrows a reference to it?
Experiment by calling `.clear()` on the vector (after declaring it mutable).
See example below.
Run it and see what happens.
Can you explain why the compiler is returning an error and the meaning of that error?*
```rust
fn main() {
let transaction_hex = "010000000242d5c1d6f7308bbe95c0f6e1301dd73a8da77d2155b0773bc297ac47f9cd7380010000006a4730440220771361aae55e84496b9e7b06e0a53dd122a1425f85840af7a52b20fa329816070220221dd92132e82ef9c133cb1a106b64893892a11acf2cfa1adb7698dcdc02f01b0121030077be25dc482e7f4abad60115416881fe4ef98af33c924cd8b20ca4e57e8bd5feffffff75c87cc5f3150eefc1c04c0246e7e0b370e64b17d6226c44b333a6f4ca14b49c000000006b483045022100e0d85fece671d367c8d442a96230954cdda4b9cf95e9edc763616d05d93e944302202330d520408d909575c5f6976cc405b3042673b601f4f2140b2e4d447e671c47012103c43afccd37aae7107f5a43f5b7b223d034e7583b77c8cd1084d86895a7341abffeffffff02ebb10f00000000001976a9144ef88a0b04e3ad6d1888da4be260d6735e0d308488ac508c1e000000000017a91476c0c8f2fc403c5edaea365f6a284317b9cdf7258700000000";
Expand Down