]>
Commit | Line | Data |
---|---|---|
13cf67c4 XL |
1 | ## An Example Program Using Structs |
2 | ||
3 | To understand when we might want to use structs, let’s write a program that | |
4 | calculates the area of a rectangle. We’ll start with single variables, and then | |
5 | refactor the program until we’re using structs instead. | |
6 | ||
7 | Let’s make a new binary project with Cargo called *rectangles* that will take | |
8 | the width and height of a rectangle specified in pixels and calculate the area | |
9 | of the rectangle. Listing 5-8 shows a short program with one way of doing | |
69743fb6 | 10 | exactly that in our project’s *src/main.rs*. |
13cf67c4 XL |
11 | |
12 | <span class="filename">Filename: src/main.rs</span> | |
13 | ||
14 | ```rust | |
74b04a01 | 15 | {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-08/src/main.rs:all}} |
13cf67c4 XL |
16 | ``` |
17 | ||
18 | <span class="caption">Listing 5-8: Calculating the area of a rectangle | |
19 | specified by separate width and height variables</span> | |
20 | ||
21 | Now, run this program using `cargo run`: | |
22 | ||
f035d41b | 23 | ```console |
74b04a01 | 24 | {{#include ../listings/ch05-using-structs-to-structure-related-data/listing-05-08/output.txt}} |
13cf67c4 XL |
25 | ``` |
26 | ||
27 | Even though Listing 5-8 works and figures out the area of the rectangle by | |
28 | calling the `area` function with each dimension, we can do better. The width | |
29 | and the height are related to each other because together they describe one | |
30 | rectangle. | |
31 | ||
32 | The issue with this code is evident in the signature of `area`: | |
33 | ||
34 | ```rust,ignore | |
74b04a01 | 35 | {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-08/src/main.rs:here}} |
13cf67c4 XL |
36 | ``` |
37 | ||
38 | The `area` function is supposed to calculate the area of one rectangle, but the | |
39 | function we wrote has two parameters. The parameters are related, but that’s | |
40 | not expressed anywhere in our program. It would be more readable and more | |
41 | manageable to group width and height together. We’ve already discussed one way | |
9fa01778 XL |
42 | we might do that in [“The Tuple Type”][the-tuple-type]<!-- ignore --> section |
43 | of Chapter 3: by using tuples. | |
13cf67c4 XL |
44 | |
45 | ### Refactoring with Tuples | |
46 | ||
69743fb6 | 47 | Listing 5-9 shows another version of our program that uses tuples. |
13cf67c4 XL |
48 | |
49 | <span class="filename">Filename: src/main.rs</span> | |
50 | ||
51 | ```rust | |
74b04a01 | 52 | {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-09/src/main.rs}} |
13cf67c4 XL |
53 | ``` |
54 | ||
55 | <span class="caption">Listing 5-9: Specifying the width and height of the | |
56 | rectangle with a tuple</span> | |
57 | ||
58 | In one way, this program is better. Tuples let us add a bit of structure, and | |
59 | we’re now passing just one argument. But in another way, this version is less | |
60 | clear: tuples don’t name their elements, so our calculation has become more | |
61 | confusing because we have to index into the parts of the tuple. | |
62 | ||
63 | It doesn’t matter if we mix up width and height for the area calculation, but | |
64 | if we want to draw the rectangle on the screen, it would matter! We would have | |
65 | to keep in mind that `width` is the tuple index `0` and `height` is the tuple | |
66 | index `1`. If someone else worked on this code, they would have to figure this | |
67 | out and keep it in mind as well. It would be easy to forget or mix up these | |
68 | values and cause errors, because we haven’t conveyed the meaning of our data in | |
69 | our code. | |
70 | ||
71 | ### Refactoring with Structs: Adding More Meaning | |
72 | ||
73 | We use structs to add meaning by labeling the data. We can transform the tuple | |
74 | we’re using into a data type with a name for the whole as well as names for the | |
69743fb6 | 75 | parts, as shown in Listing 5-10. |
13cf67c4 XL |
76 | |
77 | <span class="filename">Filename: src/main.rs</span> | |
78 | ||
79 | ```rust | |
74b04a01 | 80 | {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-10/src/main.rs}} |
13cf67c4 XL |
81 | ``` |
82 | ||
83 | <span class="caption">Listing 5-10: Defining a `Rectangle` struct</span> | |
84 | ||
85 | Here we’ve defined a struct and named it `Rectangle`. Inside the curly | |
86 | brackets, we defined the fields as `width` and `height`, both of which have | |
87 | type `u32`. Then in `main`, we created a particular instance of `Rectangle` | |
88 | that has a width of 30 and a height of 50. | |
89 | ||
90 | Our `area` function is now defined with one parameter, which we’ve named | |
91 | `rectangle`, whose type is an immutable borrow of a struct `Rectangle` | |
92 | instance. As mentioned in Chapter 4, we want to borrow the struct rather than | |
93 | take ownership of it. This way, `main` retains its ownership and can continue | |
94 | using `rect1`, which is the reason we use the `&` in the function signature and | |
95 | where we call the function. | |
96 | ||
97 | The `area` function accesses the `width` and `height` fields of the `Rectangle` | |
98 | instance. Our function signature for `area` now says exactly what we mean: | |
99 | calculate the area of `Rectangle`, using its `width` and `height` fields. This | |
100 | conveys that the width and height are related to each other, and it gives | |
101 | descriptive names to the values rather than using the tuple index values of `0` | |
102 | and `1`. This is a win for clarity. | |
103 | ||
104 | ### Adding Useful Functionality with Derived Traits | |
105 | ||
106 | It’d be nice to be able to print an instance of `Rectangle` while we’re | |
107 | debugging our program and see the values for all its fields. Listing 5-11 tries | |
108 | using the `println!` macro as we have used in previous chapters. This won’t | |
69743fb6 | 109 | work, however. |
13cf67c4 XL |
110 | |
111 | <span class="filename">Filename: src/main.rs</span> | |
112 | ||
113 | ```rust,ignore,does_not_compile | |
74b04a01 | 114 | {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-11/src/main.rs}} |
13cf67c4 XL |
115 | ``` |
116 | ||
117 | <span class="caption">Listing 5-11: Attempting to print a `Rectangle` | |
118 | instance</span> | |
119 | ||
e1599b0c | 120 | When we compile this code, we get an error with this core message: |
13cf67c4 XL |
121 | |
122 | ```text | |
74b04a01 | 123 | {{#include ../listings/ch05-using-structs-to-structure-related-data/listing-05-11/output.txt:3}} |
13cf67c4 XL |
124 | ``` |
125 | ||
69743fb6 | 126 | The `println!` macro can do many kinds of formatting, and by default, the curly |
13cf67c4 XL |
127 | brackets tell `println!` to use formatting known as `Display`: output intended |
128 | for direct end user consumption. The primitive types we’ve seen so far | |
129 | implement `Display` by default, because there’s only one way you’d want to show | |
130 | a `1` or any other primitive type to a user. But with structs, the way | |
131 | `println!` should format the output is less clear because there are more | |
132 | display possibilities: Do you want commas or not? Do you want to print the | |
133 | curly brackets? Should all the fields be shown? Due to this ambiguity, Rust | |
134 | doesn’t try to guess what we want, and structs don’t have a provided | |
135 | implementation of `Display`. | |
136 | ||
137 | If we continue reading the errors, we’ll find this helpful note: | |
138 | ||
139 | ```text | |
74b04a01 | 140 | {{#include ../listings/ch05-using-structs-to-structure-related-data/listing-05-11/output.txt:9:10}} |
13cf67c4 XL |
141 | ``` |
142 | ||
143 | Let’s try it! The `println!` macro call will now look like `println!("rect1 is | |
144 | {:?}", rect1);`. Putting the specifier `:?` inside the curly brackets tells | |
69743fb6 XL |
145 | `println!` we want to use an output format called `Debug`. The `Debug` trait |
146 | enables us to print our struct in a way that is useful for developers so we can | |
147 | see its value while we’re debugging our code. | |
13cf67c4 | 148 | |
e1599b0c | 149 | Compile the code with this change. Drat! We still get an error: |
13cf67c4 XL |
150 | |
151 | ```text | |
74b04a01 | 152 | {{#include ../listings/ch05-using-structs-to-structure-related-data/output-only-01-debug/output.txt:3}} |
13cf67c4 XL |
153 | ``` |
154 | ||
155 | But again, the compiler gives us a helpful note: | |
156 | ||
157 | ```text | |
74b04a01 | 158 | {{#include ../listings/ch05-using-structs-to-structure-related-data/output-only-01-debug/output.txt:9:10}} |
13cf67c4 XL |
159 | ``` |
160 | ||
161 | Rust *does* include functionality to print out debugging information, but we | |
162 | have to explicitly opt in to make that functionality available for our struct. | |
163 | To do that, we add the annotation `#[derive(Debug)]` just before the struct | |
69743fb6 | 164 | definition, as shown in Listing 5-12. |
13cf67c4 XL |
165 | |
166 | <span class="filename">Filename: src/main.rs</span> | |
167 | ||
168 | ```rust | |
74b04a01 | 169 | {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-12/src/main.rs}} |
13cf67c4 XL |
170 | ``` |
171 | ||
172 | <span class="caption">Listing 5-12: Adding the annotation to derive the `Debug` | |
173 | trait and printing the `Rectangle` instance using debug formatting</span> | |
174 | ||
175 | Now when we run the program, we won’t get any errors, and we’ll see the | |
176 | following output: | |
177 | ||
f035d41b | 178 | ```console |
74b04a01 | 179 | {{#include ../listings/ch05-using-structs-to-structure-related-data/listing-05-12/output.txt}} |
13cf67c4 XL |
180 | ``` |
181 | ||
182 | Nice! It’s not the prettiest output, but it shows the values of all the fields | |
183 | for this instance, which would definitely help during debugging. When we have | |
184 | larger structs, it’s useful to have output that’s a bit easier to read; in | |
185 | those cases, we can use `{:#?}` instead of `{:?}` in the `println!` string. | |
186 | When we use the `{:#?}` style in the example, the output will look like this: | |
187 | ||
f035d41b | 188 | ```console |
74b04a01 | 189 | {{#include ../listings/ch05-using-structs-to-structure-related-data/output-only-02-pretty-debug/output.txt}} |
13cf67c4 XL |
190 | ``` |
191 | ||
192 | Rust has provided a number of traits for us to use with the `derive` annotation | |
193 | that can add useful behavior to our custom types. Those traits and their | |
6a06907d XL |
194 | behaviors are listed in [Appendix C][app-c]<!-- ignore -->. We’ll cover how to |
195 | implement these traits with custom behavior as well as how to create your own | |
196 | traits in Chapter 10. | |
13cf67c4 XL |
197 | |
198 | Our `area` function is very specific: it only computes the area of rectangles. | |
199 | It would be helpful to tie this behavior more closely to our `Rectangle` | |
200 | struct, because it won’t work with any other type. Let’s look at how we can | |
201 | continue to refactor this code by turning the `area` function into an `area` | |
202 | *method* defined on our `Rectangle` type. | |
9fa01778 XL |
203 | |
204 | [the-tuple-type]: ch03-02-data-types.html#the-tuple-type | |
6a06907d | 205 | [app-c]: appendix-03-derivable-traits.md |