Skip to content

Commit 2f5ff14

Browse files
committed
Break long paragraph lines
1 parent 8e833a2 commit 2f5ff14

File tree

1 file changed

+60
-15
lines changed

1 file changed

+60
-15
lines changed

06_pointers_and_slices.md

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,65 @@
11
# Pointers and The Slice Type
22

3-
When we call `[]` on a vector to reference some set of elements, we are actually returning a slice and not another vector. A slice is just a region of contiguous data in memory. However, in Rust, we don't typically store that region of data directly into a variable. Instead we always *refer* to that region of data with the use of a pointer. In the case of a slice, we use what's called a **fat pointer**, which is a two-word value comprising a pointer to the slice's first element and the number of elements in the slice. Take a look at the diagram below.
3+
When we call `[]` on a vector to reference some set of elements, we are actually returning a slice and not another vector.
4+
A slice is just a region of contiguous data in memory.
5+
However, in Rust, we don't typically store that region of data directly into a variable.
6+
Instead we always *refer* to that region of data with the use of a pointer.
7+
In the case of a slice, we use what's called a **fat pointer**, which is a two-word value comprising a pointer to the slice's first element and the number of elements in the slice.
8+
Take a look at the diagram below.
49

510
<img src="https://www.lurklurk.org/effective-rust/images/vecslice.svg" width=500/>
611
<p><i>source: <a href="https://doc.rust-lang.org/book/ch15-00-smart-pointers.html">https://doc.rust-lang.org/book/ch15-00-smart-pointers.html</a></i></p>
712

813
### Stack and Heap
9-
If you don't work in systems programming, you probably don't spend much time thinking about the stack and the heap, so let's provide a quick overview / refresher here. The stack represents the local variables in our program execution. Those variables in turn can refer to or *point to* data on the heap which is a less structured area of memory available to our program. When we need to store large amounts of data, we typically *allocate* that data on the heap. This is useful because the heap has no memory restrictions, whereas the stack is limited. The heap also allows data to be accessed from anywhere in the program, which is useful for data shared across different functions or modules. However, allocating to the heap comes with a cost. It takes more time for the program to find the space in memory to allocate the data and do some bookkeeping to return a pointer and prepare for the next allocation. When the data needs to be accessed or updated, there is additional overhead to find the the data in memory and to also reallocate memory as needed.
10-
11-
In Rust, if a data type is *dynamically sized*, meaning it can expand or shrink and its size is not known at compile time, it *must be* allocated on the heap. If the data is a known, fixed-size and doesn't change, it can be allocated on the stack. This is for more memory safety purposes. Remember, the stack memory is limited so the compiler doesn't want there to be any surprises when the program actually runs which could lead to dangerous memory errors. Since the pointers themselves are known and fixed in size they can be safely allocated to our program's stack. The underlying data they point to is allocated on the heap.
12-
13-
Notice in the diagram above how the vector is also a pointer type to data stored on the heap. In Rust, the vector is actually just a *smart pointer*, unlike the slice, which is instead of a *fat pointer*. A smart pointer contains additional metadata and capabilities. It also *owns* the data instead of just borrowing a reference to it. We'll explore the concepts of borrowing and references in more detail later on. For now, it's enough to understand the following key points:
14-
1. Both vectors and slice references (often just called "slices" for short) **point** to the data in memory. This makes it lightweight to pass around and move these data types in the program. When they are moved, there is no need to move or copy the data on the heap as well.
15-
2. A vector indicates ownership of the memory and a slice indicates a borrowing of memory. One way to think about this is that when the vector goes out of scope and is no longer used or is "dropped", it has to deallocate all the data in memory as well. So when the smart pointer is removed, all the underlying data on the heap must be removed as well. The slice reference however can be "dropped" and the data it points to will remain in heap memory, since it is just borrowing the memory and doesn't own it.
16-
17-
Hopefully that made sense. Don't worry if it feels a bit too complicated at the moment. We'll get more familiar with these concepts as we progress in the course and get more practice. It will eventually make sense. I promise.
14+
If you don't work in systems programming, you probably don't spend much time thinking about the stack and the heap, so let's provide a quick overview / refresher here.
15+
The stack represents the local variables in our program execution.
16+
Those variables in turn can refer to or *point to* data on the heap which is a less structured area of memory available to our program.
17+
When we need to store large amounts of data, we typically *allocate* that data on the heap.
18+
This is useful because the heap has no memory restrictions, whereas the stack is limited.
19+
The heap also allows data to be accessed from anywhere in the program, which is useful for data shared across different functions or modules.
20+
However, allocating to the heap comes with a cost.
21+
It takes more time for the program to find the space in memory to allocate the data and do some bookkeeping to return a pointer and prepare for the next allocation.
22+
When the data needs to be accessed or updated, there is additional overhead to find the the data in memory and to also reallocate memory as needed.
23+
24+
In Rust, if a data type is *dynamically sized*, meaning it can expand or shrink and its size is not known at compile time, it *must be* allocated on the heap.
25+
If the data is a known, fixed-size and doesn't change, it can be allocated on the stack.
26+
This is for more memory safety purposes.
27+
Remember, the stack memory is limited so the compiler doesn't want there to be any surprises when the program actually runs which could lead to dangerous memory errors.
28+
Since the pointers themselves are known and fixed in size they can be safely allocated to our program's stack.
29+
The underlying data they point to is allocated on the heap.
30+
31+
Notice in the diagram above how the vector is also a pointer type to data stored on the heap.
32+
In Rust, the vector is actually just a *smart pointer*, unlike the slice, which is instead of a *fat pointer*.
33+
A smart pointer contains additional metadata and capabilities.
34+
It also *owns* the data instead of just borrowing a reference to it.
35+
We'll explore the concepts of borrowing and references in more detail later on.
36+
For now, it's enough to understand the following key points:
37+
1. Both vectors and slice references (often just called "slices" for short) **point** to the data in memory.
38+
This makes it lightweight to pass around and move these data types in the program.
39+
When they are moved, there is no need to move or copy the data on the heap as well.
40+
41+
2. A vector indicates ownership of the memory and a slice indicates a borrowing of memory.
42+
One way to think about this is that when the vector goes out of scope and is no longer used or is "dropped", it has to deallocate all the data in memory as well.
43+
So when the smart pointer is removed, all the underlying data on the heap must be removed as well.
44+
The slice reference however can be "dropped" and the data it points to will remain in heap memory, since it is just borrowing the memory and doesn't own it.
45+
46+
Hopefully that made sense.
47+
Don't worry if it feels a bit too complicated at the moment.
48+
We'll get more familiar with these concepts as we progress in the course and get more practice.
49+
It will eventually make sense.
50+
I promise.
1851

1952
So let's return to the error we're getting.
2053
`error[E0277]: the size for values of type [u8] cannot be known at compilation time`
2154

22-
In Rust, we cannot store dynamically sized data directly into a variable. The program doesn't know until runtime how that data will grow or change and so Rust demands that we instead allocate that data on the heap instead of the stack. Calling `[]` on a vec will return a region of dynamically-sized data, so we must always store a pointer reference to that data in a local variable. We can do this by adding the `&` in front as the compiler suggested. The `&transaction_bytes[0..4]` is now a pointer and not the actual slice data on the heap.
55+
In Rust, we cannot store dynamically sized data directly into a variable.
56+
The program doesn't know until runtime how that data will grow or change and so Rust demands that we instead allocate that data on the heap instead of the stack.
57+
Calling `[]` on a vec will return a region of dynamically-sized data, so we must always store a pointer reference to that data in a local variable.
58+
We can do this by adding the `&` in front as the compiler suggested.
59+
The `&transaction_bytes[0..4]` is now a pointer and not the actual slice data on the heap.
2360

24-
See below for our modified program. We've also added a `println!` in there to see what the version bytes looks like.
61+
See below for our modified program.
62+
We've also added a `println!` in there to see what the version bytes looks like.
2563

2664
```rust
2765
fn read_version(transaction_hex: &str) -> u32 {
@@ -37,7 +75,13 @@ fn main() {
3775
}
3876
```
3977

40-
*Note: for `println!` we can insert additional characters in the brackets to modify the how the output is displayed. `{:?}` will give us the debug output. As long as the variable's type implements the `Debug` trait, we can see the debugging printout for that variable. Not sure what a trait is? Don't worry we'll talk about them a bit more in chapter 8. For now, what this means is that only certain types offer a debug output. Fortunately, the slice reference type is one of them.*
78+
*Note: for `println!` we can insert additional characters in the brackets to modify the how the output is displayed.
79+
`{:?}` will give us the debug output.
80+
As long as the variable's type implements the `Debug` trait, we can see the debugging printout for that variable.
81+
Not sure what a trait is?
82+
Don't worry we'll talk about them a bit more in chapter 8.
83+
For now, what this means is that only certain types offer a debug output.
84+
Fortunately, the slice reference type is one of them.*
4185

4286
We can now see a printout of the bytes in addition to the version:
4387

@@ -46,10 +90,11 @@ version bytes: [1, 0, 0, 0]
4690
Version: 1
4791
```
4892

49-
Great! Let's keep moving and calculate the version number from the byte collection.
93+
Great! Let's keep moving and calculate the version number from the byte collection.
5094

5195
### Quiz
52-
*How is a String implemented in Rust? How is it different from a string slice &str?*
96+
*How is a String implemented in Rust?
97+
How is it different from a string slice &str?*
5398

5499
### Additional Reading
55100
* Stack and the Heap: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html#the-stack-and-the-heap

0 commit comments

Comments
 (0)