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