You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 08_traits_and_reading_bytes.md
+50-54Lines changed: 50 additions & 54 deletions
Original file line number
Diff line number
Diff line change
@@ -12,11 +12,44 @@ Notice how we're grabbing different ranges of `transaction_bytes`. We have to re
12
12
13
13
One way to read a byte stream is to leverage Rust's standard library's [`Read`](https://doc.rust-lang.org/std/io/trait.Read.html) trait. The slice data type in Rust implements the `Read` trait. What does this mean? Well, as we will see, it gives us a method, `read`, which will read some bytes from the slice and then store that data into a array. When we call `read` again, it will start from where it left off. In other words, it keeps track of where we are in the stream and modifies the pointer as it reads. This means we don't need to keep track of any indexes.
14
14
15
-
## Traits
15
+
Let's walk through how this works at a high level with a quick example and then dive deeper into what traits are and how they work.
16
16
17
-
We've mentioned traits a few times now, but haven't gone into detail about what they are and how they work. We'll get some more practice with them later on, but for now it's enough to understand that traits are a way to define shared behavior. You can think of them as a template for a particular behavior. For example, the `Read` trait provides a template for types that want to "read data". It lays out what to expect and what types of functions are available.
17
+
In order to use a trait method we have to first bring it into scope with a `use` statement. In this case, we want to bring the `Read` trait into scope with `use std::io::Read`. The next thing we want to do is use the `read` method as intended based on the example from the [documentation](https://doc.rust-lang.org/std/io/trait.Read.html#examples).
18
18
19
-
Let's take a closer look at [`Read` from the documentation](https://doc.rust-lang.org/std/io/trait.Read.html). It has a required method, `read`, which has the following function signature: `fn read(&mut self, buf: &mut [u8]) -> Result<usize>;`.
19
+
You can follow along with this example in [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=627e8e7c10d530819a80f189cedded13).
The `mut` keyword before `bytes_slice` tells Rust the variable is mutable. If we don't provide that keyword in a variable declaration, then the compiler will complain that we're attempting to change an immutable variable, which is not allowed.
35
+
36
+
You might also notice the `&mut` keyword in the argument to the `read` method. This indicates that we're passing in `buffer` as a *mutable reference*. We'll talk more about this means in the next chapter so for now let's not worry about that nuance.
37
+
38
+
When we run this, it will print the following:
39
+
```console
40
+
Version: 1
41
+
Bytes slice: [2]
42
+
```
43
+
44
+
And this is what we'd expect. The Version is `1`. And the `bytes_slice` variable has been updated and no longer contains the first 4 bytes.
45
+
46
+
You may notice that the way this works is that you have to first create an array with a fixed size. Calling `read` will then extract the number of bytes equal to the size of the array, store that into a buffer and then update our slice.
47
+
48
+
## Traits Explanation
49
+
50
+
So what are traits exactly? Traits are a way to define shared behavior. You can think of them as a template for a particular set of behaviors. For example, the `Read` trait provides a template for types that want to "read data". It lays out what to expect and what types of functions are available.
51
+
52
+
Let's take a closer look at the `Read` trait [from the documentation](https://doc.rust-lang.org/std/io/trait.Read.html). It has a required method, `read`, which has the following function signature: `fn read(&mut self, buf: &mut [u8]) -> Result<usize>;`.
20
53
21
54
```rust
22
55
...
@@ -28,9 +61,13 @@ pub trait Read {
28
61
...
29
62
```
30
63
31
-
The functions themselves are not actually implemented with any logic. You'll notice there's no function body, just the signature. The types that implement this trait are expected to provide the function logic for each of these methods. So the trait is really just a template.
64
+
The `read` method itself is not actually implemented with any logic. You'll notice there's no function body, just the signature. The types that "implement" this trait are expected to provide the function logic for any *required* method, or trait methods that have no implementation.
65
+
66
+
A trait can also provide other methods that a type can get access to once it has implemented the trait. These are known as *provided* methods and are considered *default* implementations since they can also be overwritten. You'll notice for example that there is a [`read_exact` method](https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact) which is implemented with a call to the [`default_read_exact`](https://doc.rust-lang.org/src/std/io/mod.rs.html#558) method.
32
67
33
-
Now, if we look at the `slice` type from the documentation, we can see that it [*implements*`Read`](https://doc.rust-lang.org/std/primitive.slice.html#impl-Read-for-%26%5Bu8%5D) meaning it provides the function logic for the given trait template. Let's take a look at the [implementation](https://doc.rust-lang.org/src/std/io/impls.rs.html#235-250):
68
+
As long as a type implements the `Read` trait by providing a `read` method, it will have access to these other *provided* methods. A type can also choose to override some or all of these *provided* methods as well and have its own implementations.
69
+
70
+
Now if we look at the `slice` type from the documentation, we can see that it [*implements* the `Read` trait](https://doc.rust-lang.org/std/primitive.slice.html#impl-Read-for-%26%5Bu8%5D) and provides the function logic for the `read` method. Let's take a look at [the source code](https://doc.rust-lang.org/src/std/io/impls.rs.html#235-250):
34
71
35
72
```rust
36
73
...
@@ -58,54 +95,12 @@ impl Read for &[u8] {
58
95
...
59
96
```
60
97
61
-
Don't worry if you don't understand how to read all of this just yet! Simply notice how we *implement* a trait with the `impl` keyword. So `impl Read for &[u8]` is the code block that provides the function logic for the trait. The other thing to notice is how the function signature matches the trait's function signature.
62
-
63
-
The idea here is that different types, not just the `&[u8]` type can implement the `Read` trait and then be expected to have a similar behavior. The function logic itself for each type might differ, but they are expected to take in the same arguments, return the same type and generally do the same thing, which in this case is to read some data and modify `self` and the buffer. You might notice some patterns here that you are not yet familiar with, such as the `&mut` keyword and asterisk `*` before `self` at the bottom of the function. Don't worry, we'll go into more detail about what these mean in the next lesson.
64
-
65
-
For now, let's experiment by following the second example from the [documentation for Read](https://doc.rust-lang.org/std/io/trait.Read.html#examples). We'll comment out our original code and experiment with these new lines:
// let version = read_version("010000000242d5c1d6f7308bbe95c0f6e1301dd73a8da77d2155b0773bc297ac47f9cd7380010000006a4730440220771361aae55e84496b9e7b06e0a53dd122a1425f85840af7a52b20fa329816070220221dd92132e82ef9c133cb1a106b64893892a11acf2cfa1adb7698dcdc02f01b0121030077be25dc482e7f4abad60115416881fe4ef98af33c924cd8b20ca4e57e8bd5feffffff75c87cc5f3150eefc1c04c0246e7e0b370e64b17d6226c44b333a6f4ca14b49c000000006b483045022100e0d85fece671d367c8d442a96230954cdda4b9cf95e9edc763616d05d93e944302202330d520408d909575c5f6976cc405b3042673b601f4f2140b2e4d447e671c47012103c43afccd37aae7107f5a43f5b7b223d034e7583b77c8cd1084d86895a7341abffeffffff02ebb10f00000000001976a9144ef88a0b04e3ad6d1888da4be260d6735e0d308488ac508c1e000000000017a91476c0c8f2fc403c5edaea365f6a284317b9cdf7258700000000");
78
-
// println!("Version: {}", version);
79
-
}
80
-
```
81
-
82
-
Try running this now with `cargo run`. This fail to compile. You'll get a compile error that the `read` method is not found for `&[u8]`. This is because the trait implementations only become available when the trait is brought into scope with a `use` import. So you just need to add a `use std::io::Read;` line at the top. Let's add that and run this again.
This is a straightforward idea that we haven't talked about until now, but in Rust if any variable is going to be modified, we have to explicitly declare it as *mutable* with the `mut` keyword. This just means that the variable is allowed to change. The `read` method will attempt to modify our `bytes_slice` so we have to declare it as *mutable*.
99
-
100
-
Ok, if we add that and run it again, it should work and will print out the following:
101
-
```shell
102
-
Version: 1
103
-
Bytes Slice: [2]
104
-
```
98
+
Don't worry if you don't understand what all of this means just yet! Simply notice how we *implement* a trait with the `impl` keyword. So `impl Read for &[u8]` is the code block that provides the function logic for the trait. The other thing to notice is how the function signature for `read` matches the trait's function signature.
105
99
106
-
We converted the 4 bytes from the buffer into an unsigned 32-bit integer. And notice how the bytes slice has been modified after being read into the buffer. It no longer contains the first 4 elements, `[1, 0, 0, 0]`.
100
+
The idea here is that different types, not just the `&[u8]` type can implement the `Read` trait by providing the function logic for any required method and then be expected to have similar behavior and get access to the trait's provided methods.
101
+
The function logic itself for each type might differ, but given the template they are expected to take in the same arguments, return the same type and generally do the same thing, which in this case is to read some data and modify `self` and the buffer.
107
102
108
-
You may notice that the way this works is that you have to first create an array with a known size. Calling `read` will then extract the number of bytes equal to the size of the array, store that into our buffer and then modify our fat pointer reference to the underlying data.
103
+
Again, you might notice some patterns in the code above that you are not yet familiar with, such as the `&mut` keyword and asterisk `*` before `self` at the bottom of the function. We'll go into more detail about what these mean in the next lesson.
109
104
110
105
Let's now update our program to print out the version number leveraging the `Read` trait. We can convert the `transaction_bytes``Vec` to a `slice` type using the `as_slice` method. Here is the modified `read_version` function.
111
106
@@ -129,12 +124,13 @@ fn main() {
129
124
}
130
125
```
131
126
132
-
And voila, this will print `Version: 1` as expected!
127
+
And voila, this will print `Version: 1` as expected! Great job so far!
133
128
134
-
But this doesn't seem ideal. How do we grab the modified `bytes_slice` and continue decoding the transaction? What we probably want to do is pass in the `bytes_slice` into this function as an argument and continue using it in the `main` function. We'll talk more about that and associated Rust concepts of references and borrowing in the next section.
129
+
How do we grab the modified `bytes_slice` and continue decoding the transaction? What we probably want to do is pass in the `bytes_slice` into this function as an argument and continue using it in the `main` function. We'll talk more about that and associated Rust concepts of references and borrowing in the next section.
135
130
136
131
### Quiz
137
-
*Consider the following block of code in which we create a Vec and then attempt to print it out:*
132
+
1.*Take another look at the `Read` trait and the implementation of the `Read` trait for a slice in the documentation. What are the required and provided methods for the trait? What provided methods are being overwritten by the slice?*
133
+
2.*Consider the following block of code in which we create a Vec and then attempt to print it out:*
Copy file name to clipboardExpand all lines: 09_00_mutable_references.md
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -18,7 +18,7 @@ fn main() {
18
18
19
19
Notice how we first decode the hex string to get a `vec` of bytes. We then convert that into a slice and then pass that into the `read_version` function.
20
20
21
-
We'll now have to modify the `read_version` function in order to accept the correct argument. What do you think is the correct type for the the argument? Take a moment and then check back here.
21
+
We'll now have to modify the `read_version` function to accept the correct argument. What do you think is the correct type for the the argument? Take a moment and then check back here.
Copy file name to clipboardExpand all lines: quiz_solutions/08_solution.md
+10-4Lines changed: 10 additions & 4 deletions
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,9 @@
1
1
# 8 Solution
2
2
3
3
### Quiz
4
-
*Consider the following block of code in which we create a Vec and then attempt to print it out:*
4
+
1.*Take another look at the `Read` trait and the implementation of the `Read` trait for a slice in the documentation. What are the required and provided methods for the trait? What provided methods are being overwritten by the slice?*
5
+
6
+
2.*Consider the following block of code in which we create a Vec and then attempt to print it out:*
5
7
```rust
6
8
fnmain() {
7
9
letvec:Vec::<u8> =vec![0, 0, 0, 0, 0];
@@ -14,7 +16,9 @@ fn main() {
14
16
*3. Try and implement the correct trait for Vec so that it can be printed for standard display purposes.*
15
17
16
18
### Solution
17
-
If we run this and look at the compiler error, we'll get a better sense of what's going on here.
19
+
1. According to the [`Read` trait documentation](https://doc.rust-lang.org/std/io/trait.Read.html), `read` is the only required method. The rest, `read_vectored`, `is_read_vectored`, `read_to_end`, `read_to_string`, `read_exact`, `read_buf`, `read_buf_exact`, `by_ref`, `bytes`, `chain`, and `take` are all provided methods with default implementations. The provided methods that the [slice](https://doc.rust-lang.org/src/std/io/impls.rs.html#233-323) overwrites are `read_vectored`, `is_read_vectored`, `read_to_end`, `read_to_string`, `read_exact` and `read_buf`.
20
+
21
+
2. If we run this and look at the compiler error, we'll get a better sense of what's going on here.
For now, if we wanted to print a `vec` we would have to use `{:?}` inside the println! string slice argument. This will print the `Debug` output and a `vec` does implement `Debug`.
40
44
41
-
Let's attempt to implement the `Display` trait for a `vec`:
45
+
Let's attempt to implement the `Display` trait for a `vec` by implementing the required method `fmt`:
42
46
```rust
43
47
usestd::fmt;
44
48
@@ -58,4 +62,6 @@ fn main() {
58
62
}
59
63
```
60
64
61
-
The basic idea is that we leverage the `write!` macro which takes the `Formatter` instance and writes some information to it. If any step fails, an error will be returned. Otherwise, if we iterate through the vector and are able to write all values successfully we can simply return the `Ok(())` result. This might still be a bit confusing at this stage, so consider coming back to revisit this solution after you've gone through the course.
65
+
The basic idea is that we leverage the `write!` macro which takes the `Formatter` instance and writes some information to it. If any step fails, an error will be returned (we'll talk more about error handling and the `?` operator in chapter 19). If we iterate through the vector and are able to write all values successfully we can simply return the `Ok(())` result, which matches the the expected result type `fmt::Result`.
66
+
67
+
This might still be a bit confusing at this stage, so consider coming back to revisit this solution after you've gone through the course.
0 commit comments