]>
Commit | Line | Data |
---|---|---|
cc61c64b XL |
1 | |
2 | [TOC] | |
3 | ||
7cac9316 XL |
4 | # Using Structs to Structure Related Data |
5 | ||
6 | A *struct*, or *structure*, is a custom data type that lets us name and package | |
7 | together multiple related values that make up a meaningful group. If you’re | |
8 | familiar with an object-oriented language, a *struct* is like an object’s data | |
3b2f2976 | 9 | attributes. In this chapter, we’ll compare and contrast tuples with structs, |
7cac9316 | 10 | demonstrate how to use structs, and discuss how to define methods and |
ea8adc8c XL |
11 | associated functions to specify behavior associated with a struct’s data. The |
12 | struct and *enum* (which is discussed in Chapter 6) concepts are the building | |
13 | blocks for creating new types in your program’s domain to take full advantage | |
14 | of Rust’s compile time type checking. | |
7cac9316 XL |
15 | |
16 | ## Defining and Instantiating Structs | |
17 | ||
18 | Structs are similar to tuples, which were discussed in Chapter 3. Like tuples, | |
19 | the pieces of a struct can be different types. Unlike tuples, we name each | |
20 | piece of data so it’s clear what the values mean. As a result of these names, | |
21 | structs are more flexible than tuples: we don’t have to rely on the order of | |
22 | the data to specify or access the values of an instance. | |
23 | ||
24 | To define a struct, we enter the keyword `struct` and name the entire struct. A | |
25 | struct’s name should describe the significance of the pieces of data being | |
26 | grouped together. Then, inside curly braces, we define the names and types of | |
27 | the pieces of data, which we call *fields*. For example, Listing 5-1 shows a | |
28 | struct to store information about a user account: | |
cc61c64b | 29 | |
3b2f2976 | 30 | ``` |
cc61c64b XL |
31 | struct User { |
32 | username: String, | |
33 | email: String, | |
34 | sign_in_count: u64, | |
35 | active: bool, | |
36 | } | |
37 | ``` | |
38 | ||
cc61c64b | 39 | Listing 5-1: A `User` struct definition |
7cac9316 XL |
40 | |
41 | To use a struct after we’ve defined it, we create an *instance* of that struct | |
42 | by specifying concrete values for each of the fields. We create an instance by | |
43 | stating the name of the struct, and then add curly braces containing `key: | |
44 | value` pairs where the keys are the names of the fields and the values are the | |
45 | data we want to store in those fields. We don’t have to specify the fields in | |
46 | the same order in which we declared them in the struct. In other words, the | |
47 | struct definition is like a general template for the type, and instances fill | |
48 | in that template with particular data to create values of the type. For | |
49 | example, we can declare a particular user as shown in Listing 5-2: | |
cc61c64b | 50 | |
3b2f2976 | 51 | ``` |
cc61c64b XL |
52 | let user1 = User { |
53 | email: String::from("someone@example.com"), | |
54 | username: String::from("someusername123"), | |
55 | active: true, | |
56 | sign_in_count: 1, | |
57 | }; | |
58 | ``` | |
59 | ||
7cac9316 XL |
60 | Listing 5-2: Creating an instance of the `User` struct |
61 | ||
62 | To get a specific value from a struct, we can use dot notation. If we wanted | |
63 | just this user’s email address, we can use `user1.email` wherever we want to | |
ea8adc8c XL |
64 | use this value. If the instance is mutable, we can change a value by using the |
65 | dot notation and assigning into a particular field. Listing 5-3 shows how to | |
66 | change the value in the `email` field of a mutable `User` instance: | |
041b39d2 XL |
67 | |
68 | ``` | |
69 | let mut user1 = User { | |
70 | email: String::from("someone@example.com"), | |
71 | username: String::from("someusername123"), | |
72 | active: true, | |
73 | sign_in_count: 1, | |
74 | }; | |
75 | ||
76 | user1.email = String::from("anotheremail@example.com"); | |
77 | ``` | |
78 | ||
79 | Listing 5-3: Changing the value in the `email` field of a `User` instance | |
7cac9316 | 80 | |
ea8adc8c XL |
81 | Note that the entire instance must be mutable; Rust doesn’t allow us to mark |
82 | only certain fields as mutable. Also note that with any expression, we can | |
83 | construct a new instance of the struct as the last expression in the function | |
84 | body to implicitly return that new instance. | |
85 | ||
86 | Listing 5-4 shows a `build_user` function that returns a `User` instance with | |
87 | the given email and username. The `active` field gets the value of `true`, and | |
88 | the `sign_in_count` gets a value of `1`. | |
7cac9316 | 89 | |
3b2f2976 | 90 | ``` |
7cac9316 XL |
91 | fn build_user(email: String, username: String) -> User { |
92 | User { | |
93 | email: email, | |
94 | username: username, | |
95 | active: true, | |
96 | sign_in_count: 1, | |
97 | } | |
98 | } | |
99 | ``` | |
100 | ||
041b39d2 | 101 | Listing 5-4: A `build_user` function that takes an email and username and |
7cac9316 XL |
102 | returns a `User` instance |
103 | ||
ea8adc8c XL |
104 | It makes sense to name the function arguments with the same name as the struct |
105 | fields, but having to repeat the `email` and `username` field names and | |
106 | variables is a bit tedious. If the struct had more fields, repeating each name | |
107 | would get even more annoying. Luckily, there's a convenient shorthand! | |
3b2f2976 | 108 | |
ea8adc8c | 109 | ### Using the Field Init Shorthand when Variables and Fields Have the Same Name |
3b2f2976 | 110 | |
ea8adc8c XL |
111 | Because the parameter names and the struct field names are exactly the same in |
112 | Listing 5-4, we can use the *field init shorthand* syntax to rewrite | |
113 | `build_user` so that it behaves exactly the same but doesn’t have the | |
114 | repetition of `email` and `username` in the way shown in Listing 5-5. | |
3b2f2976 XL |
115 | |
116 | ``` | |
7cac9316 XL |
117 | fn build_user(email: String, username: String) -> User { |
118 | User { | |
119 | email, | |
120 | username, | |
121 | active: true, | |
122 | sign_in_count: 1, | |
123 | } | |
124 | } | |
125 | ``` | |
126 | ||
ea8adc8c | 127 | Listing 5-5: A `build_user` function that uses field init shorthand since the |
7cac9316 XL |
128 | `email` and `username` parameters have the same name as struct fields |
129 | ||
ea8adc8c XL |
130 | Here, we’re creating a new instance of the `User` struct, which has a field |
131 | named `email`. We want to set the `email` field’s value to the value in the | |
132 | `email` parameter of the `build_user` function. Because the `email` field and | |
133 | the `email` parameter have the same name, we only need to write `email` rather | |
134 | than `email: email`. | |
135 | ||
7cac9316 XL |
136 | ### Creating Instances From Other Instances With Struct Update Syntax |
137 | ||
ea8adc8c XL |
138 | It’s often useful to create a new instance of a struct that uses most of an old |
139 | instance’s values, but changes some. We do this using *struct update syntax*. | |
140 | ||
141 | First, Listing 5-6 shows how we create a new `User` instance in `user2` without | |
142 | the update syntax. We set new values for `email` and `username`, but otherwise | |
143 | use the same values from `user1` that we created in Listing 5-2: | |
7cac9316 | 144 | |
3b2f2976 | 145 | ``` |
7cac9316 XL |
146 | let user2 = User { |
147 | email: String::from("another@example.com"), | |
148 | username: String::from("anotherusername567"), | |
149 | active: user1.active, | |
150 | sign_in_count: user1.sign_in_count, | |
151 | }; | |
152 | ``` | |
153 | ||
ea8adc8c XL |
154 | Listing 5-6: Creating a new `User` instance using some of the values from |
155 | `user1` | |
cc61c64b | 156 | |
ea8adc8c XL |
157 | Using struct update syntax, we can achieve the same effect with less code, |
158 | shown in Listing 5-7. The syntax `..` specifies that the remaining fields not | |
159 | explicitly set should have the same value as the fields in the given instance. | |
7cac9316 | 160 | |
3b2f2976 | 161 | ``` |
7cac9316 XL |
162 | let user2 = User { |
163 | email: String::from("another@example.com"), | |
164 | username: String::from("anotherusername567"), | |
165 | ..user1 | |
166 | }; | |
167 | ``` | |
168 | ||
041b39d2 | 169 | Listing 5-7: Using struct update syntax to set a new `email` and `username` |
7cac9316 XL |
170 | values for a `User` instance but use the rest of the values from the fields of |
171 | the instance in the `user1` variable | |
172 | ||
ea8adc8c XL |
173 | The code in Listing 5-7 also creates an instance in `user2` that has a |
174 | different value for `email` and `username` but has the same values for the | |
175 | `active` and `sign_in_count` fields from `user1`. | |
176 | ||
7cac9316 XL |
177 | ### Tuple Structs without Named Fields to Create Different Types |
178 | ||
179 | We can also define structs that look similar to tuples, called *tuple structs*, | |
3b2f2976 | 180 | that have the added meaning the struct name provides, but don’t have names |
ea8adc8c XL |
181 | associated with their fields, just the types of the fields. Tuple structs are |
182 | useful when you want to give the whole tuple a name and make the tuple be a | |
183 | different type than other tuples, but naming each field as in a regular struct | |
184 | would be verbose or redundant. | |
185 | ||
186 | To define a tuple struct you start with the `struct` keyword and the struct | |
187 | name followed by the types in the tuple. For example, here are definitions and | |
188 | usages of two tuple structs named `Color` and `Point`: | |
7cac9316 | 189 | |
3b2f2976 | 190 | ``` |
7cac9316 XL |
191 | struct Color(i32, i32, i32); |
192 | struct Point(i32, i32, i32); | |
193 | ||
194 | let black = Color(0, 0, 0); | |
195 | let origin = Point(0, 0, 0); | |
196 | ``` | |
197 | ||
3b2f2976 | 198 | Note that the `black` and `origin` values are different types, since they’re |
7cac9316 | 199 | instances of different tuple structs. Each struct we define is its own type, |
ea8adc8c XL |
200 | even though the fields within the struct have the same types. For example, a |
201 | function that takes a parameter of type `Color` cannot take a `Point` as an | |
202 | argument, even though both types are made up of three `i32` values. Otherwise, | |
203 | tuple struct instances behave like tuples, which we covered in Chapter 3: you | |
204 | can destructure them into their individual pieces, you can use a `.` followed | |
205 | by the index to access an individual value, and so on. | |
7cac9316 XL |
206 | |
207 | ### Unit-Like Structs without Any Fields | |
208 | ||
3b2f2976 | 209 | We can also define structs that don’t have any fields! These are called |
7cac9316 XL |
210 | *unit-like structs* since they behave similarly to `()`, the unit type. |
211 | Unit-like structs can be useful in situations such as when you need to | |
3b2f2976 XL |
212 | implement a trait on some type, but you don’t have any data that you want to |
213 | store in the type itself. We’ll be discussing traits in Chapter 10. | |
7cac9316 XL |
214 | |
215 | PROD: START BOX | |
216 | ||
217 | ### Ownership of Struct Data | |
cc61c64b | 218 | |
ea8adc8c XL |
219 | In the `User` struct definition in Listing 5-1, we used the owned `String` type |
220 | rather than the `&str` string slice type. This is a deliberate choice because | |
221 | we want instances of this struct to own all of its data and for that data to be | |
222 | valid for as long as the entire struct is valid. | |
cc61c64b | 223 | |
7cac9316 XL |
224 | It’s possible for structs to store references to data owned by something else, |
225 | but to do so requires the use of *lifetimes*, a Rust feature that is discussed | |
226 | in Chapter 10. Lifetimes ensure that the data referenced by a struct is valid | |
227 | for as long as the struct is. Let’s say you try to store a reference in a | |
228 | struct without specifying lifetimes, like this: | |
cc61c64b XL |
229 | |
230 | Filename: src/main.rs | |
231 | ||
3b2f2976 | 232 | ``` |
cc61c64b XL |
233 | struct User { |
234 | username: &str, | |
235 | email: &str, | |
236 | sign_in_count: u64, | |
237 | active: bool, | |
238 | } | |
239 | ||
240 | fn main() { | |
241 | let user1 = User { | |
242 | email: "someone@example.com", | |
243 | username: "someusername123", | |
244 | active: true, | |
245 | sign_in_count: 1, | |
246 | }; | |
247 | } | |
248 | ``` | |
249 | ||
250 | The compiler will complain that it needs lifetime specifiers: | |
251 | ||
3b2f2976 | 252 | ``` |
cc61c64b XL |
253 | error[E0106]: missing lifetime specifier |
254 | --> | |
255 | | | |
256 | 2 | username: &str, | |
257 | | ^ expected lifetime parameter | |
258 | ||
259 | error[E0106]: missing lifetime specifier | |
260 | --> | |
261 | | | |
262 | 3 | email: &str, | |
263 | | ^ expected lifetime parameter | |
264 | ``` | |
265 | ||
ea8adc8c XL |
266 | We’ll discuss how to fix these errors so you can store references in structs in |
267 | Chapter 10, but for now, we’ll fix errors like these using owned types like | |
7cac9316 XL |
268 | `String` instead of references like `&str`. |
269 | ||
270 | PROD: END BOX | |
cc61c64b | 271 | |
7cac9316 | 272 | ## An Example Program Using Structs |
cc61c64b XL |
273 | |
274 | To understand when we might want to use structs, let’s write a program that | |
7cac9316 XL |
275 | calculates the area of a rectangle. We’ll start with single variables, and then |
276 | refactor the program until we’re using structs instead. | |
cc61c64b XL |
277 | |
278 | Let’s make a new binary project with Cargo called *rectangles* that will take | |
ea8adc8c | 279 | the width and height of a rectangle specified in pixels and will calculate the |
041b39d2 | 280 | area of the rectangle. Listing 5-8 shows a short program with one way of doing |
cc61c64b XL |
281 | just that in our project’s *src/main.rs*: |
282 | ||
283 | Filename: src/main.rs | |
284 | ||
3b2f2976 | 285 | ``` |
cc61c64b | 286 | fn main() { |
cc61c64b | 287 | let width1 = 30; |
ea8adc8c | 288 | let height1 = 50; |
cc61c64b XL |
289 | |
290 | println!( | |
291 | "The area of the rectangle is {} square pixels.", | |
ea8adc8c | 292 | area(width1, height1) |
cc61c64b XL |
293 | ); |
294 | } | |
295 | ||
ea8adc8c XL |
296 | fn area(width: u32, height: u32) -> u32 { |
297 | width * height | |
cc61c64b XL |
298 | } |
299 | ``` | |
300 | ||
ea8adc8c XL |
301 | Listing 5-8: Calculating the area of a rectangle specified by its width and |
302 | height in separate variables | |
cc61c64b | 303 | |
7cac9316 | 304 | Now, run this program using `cargo run`: |
cc61c64b | 305 | |
3b2f2976 | 306 | ``` |
cc61c64b XL |
307 | The area of the rectangle is 1500 square pixels. |
308 | ``` | |
309 | ||
310 | ### Refactoring with Tuples | |
311 | ||
041b39d2 | 312 | Even though Listing 5-8 works and figures out the area of the rectangle by |
ea8adc8c XL |
313 | calling the `area` function with each dimension, we can do better. The width |
314 | and the height are related to each other because together they describe one | |
cc61c64b XL |
315 | rectangle. |
316 | ||
317 | The issue with this method is evident in the signature of `area`: | |
318 | ||
3b2f2976 | 319 | ``` |
ea8adc8c | 320 | fn area(width: u32, height: u32) -> u32 { |
cc61c64b XL |
321 | ``` |
322 | ||
7cac9316 XL |
323 | The `area` function is supposed to calculate the area of one rectangle, but the |
324 | function we wrote has two parameters. The parameters are related, but that’s | |
325 | not expressed anywhere in our program. It would be more readable and more | |
ea8adc8c | 326 | manageable to group width and height together. We’ve already discussed one way |
7cac9316 | 327 | we might do that in the Grouping Values into Tuples section of Chapter 3 on |
041b39d2 | 328 | page XX: by using tuples. Listing 5-9 shows another version of our program that |
7cac9316 | 329 | uses tuples: |
cc61c64b XL |
330 | |
331 | Filename: src/main.rs | |
332 | ||
3b2f2976 | 333 | ``` |
cc61c64b | 334 | fn main() { |
ea8adc8c | 335 | let rect1 = (30, 50); |
cc61c64b XL |
336 | |
337 | println!( | |
338 | "The area of the rectangle is {} square pixels.", | |
339 | area(rect1) | |
340 | ); | |
341 | } | |
342 | ||
343 | fn area(dimensions: (u32, u32)) -> u32 { | |
344 | dimensions.0 * dimensions.1 | |
345 | } | |
346 | ``` | |
347 | ||
ea8adc8c | 348 | Listing 5-8: Specifying the width and height of the rectangle with a tuple |
cc61c64b | 349 | |
7cac9316 XL |
350 | In one way, this program is better. Tuples let us add a bit of structure, and |
351 | we’re now passing just one argument. But in another way this version is less | |
352 | clear: tuples don’t name their elements, so our calculation has become more | |
353 | confusing because we have to index into the parts of the tuple. | |
cc61c64b | 354 | |
ea8adc8c | 355 | It doesn’t matter if we mix up width and height for the area calculation, but |
7cac9316 | 356 | if we want to draw the rectangle on the screen, it would matter! We would have |
ea8adc8c | 357 | to keep in mind that `width` is the tuple index `0` and `height` is the tuple |
7cac9316 XL |
358 | index `1`. If someone else worked on this code, they would have to figure this |
359 | out and keep it in mind as well. It would be easy to forget or mix up these | |
360 | values and cause errors, because we haven’t conveyed the meaning of our data in | |
361 | our code. | |
cc61c64b XL |
362 | |
363 | ### Refactoring with Structs: Adding More Meaning | |
364 | ||
7cac9316 XL |
365 | We use structs to add meaning by labeling the data. We can transform the tuple |
366 | we’re using into a data type with a name for the whole as well as names for the | |
041b39d2 | 367 | parts, as shown in Listing 5-10: |
cc61c64b XL |
368 | |
369 | Filename: src/main.rs | |
370 | ||
3b2f2976 | 371 | ``` |
cc61c64b | 372 | struct Rectangle { |
cc61c64b | 373 | width: u32, |
ea8adc8c | 374 | height: u32, |
cc61c64b XL |
375 | } |
376 | ||
377 | fn main() { | |
ea8adc8c | 378 | let rect1 = Rectangle { width: 30, height: 50 }; |
cc61c64b XL |
379 | |
380 | println!( | |
381 | "The area of the rectangle is {} square pixels.", | |
382 | area(&rect1) | |
383 | ); | |
384 | } | |
385 | ||
386 | fn area(rectangle: &Rectangle) -> u32 { | |
ea8adc8c | 387 | rectangle.width * rectangle.height |
cc61c64b XL |
388 | } |
389 | ``` | |
390 | ||
041b39d2 | 391 | Listing 5-10: Defining a `Rectangle` struct |
cc61c64b | 392 | |
7cac9316 | 393 | Here we’ve defined a struct and named it `Rectangle`. Inside the `{}` we |
ea8adc8c XL |
394 | defined the fields as `width` and `height`, both of which have type `u32`. Then |
395 | in `main` we create a particular instance of a `Rectangle` that has a width of | |
396 | 30 and a height of 50. | |
cc61c64b | 397 | |
7cac9316 XL |
398 | Our `area` function is now defined with one parameter, which we’ve named |
399 | `rectangle`, whose type is an immutable borrow of a struct `Rectangle` | |
400 | instance. As mentioned in Chapter 4, we want to borrow the struct rather than | |
401 | take ownership of it. This way, `main` retains its ownership and can continue | |
402 | using `rect1`, which is the reason we use the `&` in the function signature and | |
403 | where we call the function. | |
cc61c64b | 404 | |
ea8adc8c | 405 | The `area` function accesses the `width` and `height` fields of the `Rectangle` |
3b2f2976 | 406 | instance. Our function signature for `area` now indicates exactly what we mean: |
ea8adc8c XL |
407 | calculate the area of a `Rectangle` using its `width` and `height` fields. This |
408 | conveys that the width and height are related to each other, and gives | |
3b2f2976 XL |
409 | descriptive names to the values rather than using the tuple index values of `0` |
410 | and `1`—a win for clarity. | |
cc61c64b XL |
411 | |
412 | ### Adding Useful Functionality with Derived Traits | |
413 | ||
7cac9316 XL |
414 | It would be helpful to be able to print out an instance of the `Rectangle` |
415 | while we’re debugging our program in order to see the values for all its | |
041b39d2 | 416 | fields. Listing 5-11 uses the `println!` macro as we have been in earlier |
7cac9316 | 417 | chapters: |
cc61c64b XL |
418 | |
419 | Filename: src/main.rs | |
420 | ||
3b2f2976 | 421 | ``` |
cc61c64b | 422 | struct Rectangle { |
cc61c64b | 423 | width: u32, |
ea8adc8c | 424 | height: u32, |
cc61c64b XL |
425 | } |
426 | ||
427 | fn main() { | |
ea8adc8c | 428 | let rect1 = Rectangle { width: 30, height: 50 }; |
cc61c64b XL |
429 | |
430 | println!("rect1 is {}", rect1); | |
431 | } | |
432 | ``` | |
433 | ||
041b39d2 | 434 | Listing 5-11: Attempting to print a `Rectangle` instance |
cc61c64b | 435 | |
7cac9316 | 436 | When we run this code, we get an error with this core message: |
cc61c64b | 437 | |
3b2f2976 | 438 | ``` |
cc61c64b XL |
439 | error[E0277]: the trait bound `Rectangle: std::fmt::Display` is not satisfied |
440 | ``` | |
441 | ||
442 | The `println!` macro can do many kinds of formatting, and by default, `{}` | |
443 | tells `println!` to use formatting known as `Display`: output intended for | |
7cac9316 XL |
444 | direct end user consumption. The primitive types we’ve seen so far implement |
445 | `Display` by default, because there’s only one way you’d want to show a `1` or | |
446 | any other primitive type to a user. But with structs, the way `println!` should | |
447 | format the output is less clear because there are more display possibilities: | |
448 | do you want commas or not? Do you want to print the curly braces? Should all | |
449 | the fields be shown? Due to this ambiguity, Rust doesn’t try to guess what we | |
450 | want and structs don’t have a provided implementation of `Display`. | |
cc61c64b | 451 | |
7cac9316 | 452 | If we continue reading the errors, we’ll find this helpful note: |
cc61c64b | 453 | |
3b2f2976 | 454 | ``` |
cc61c64b XL |
455 | note: `Rectangle` cannot be formatted with the default formatter; try using |
456 | `:?` instead if you are using a format string | |
457 | ``` | |
458 | ||
7cac9316 XL |
459 | Let’s try it! The `println!` macro call will now look like `println!("rect1 is |
460 | {:?}", rect1);`. Putting the specifier `:?` inside the `{}` tells `println!` we | |
461 | want to use an output format called `Debug`. `Debug` is a trait that enables us | |
462 | to print out our struct in a way that is useful for developers so we can see | |
463 | its value while we’re debugging our code. | |
cc61c64b | 464 | |
7cac9316 | 465 | Run the code with this change. Drat! We still get an error: |
cc61c64b | 466 | |
3b2f2976 | 467 | ``` |
cc61c64b XL |
468 | error: the trait bound `Rectangle: std::fmt::Debug` is not satisfied |
469 | ``` | |
470 | ||
7cac9316 | 471 | But again, the compiler gives us a helpful note: |
cc61c64b | 472 | |
3b2f2976 | 473 | ``` |
cc61c64b XL |
474 | note: `Rectangle` cannot be formatted using `:?`; if it is defined in your |
475 | crate, add `#[derive(Debug)]` or manually implement it | |
476 | ``` | |
477 | ||
478 | Rust *does* include functionality to print out debugging information, but we | |
7cac9316 XL |
479 | have to explicitly opt-in to make that functionality available for our struct. |
480 | To do that, we add the annotation `#[derive(Debug)]` just before the struct | |
041b39d2 | 481 | definition, as shown in Listing 5-12: |
7cac9316 XL |
482 | |
483 | Filename: src/main.rs | |
cc61c64b | 484 | |
3b2f2976 | 485 | ``` |
cc61c64b XL |
486 | #[derive(Debug)] |
487 | struct Rectangle { | |
cc61c64b | 488 | width: u32, |
ea8adc8c | 489 | height: u32, |
cc61c64b XL |
490 | } |
491 | ||
492 | fn main() { | |
ea8adc8c | 493 | let rect1 = Rectangle { width: 30, height: 50 }; |
cc61c64b XL |
494 | |
495 | println!("rect1 is {:?}", rect1); | |
496 | } | |
497 | ``` | |
498 | ||
041b39d2 | 499 | Listing 5-12: Adding the annotation to derive the `Debug` trait and printing |
7cac9316 | 500 | the `Rectangle` instance using debug formatting |
cc61c64b | 501 | |
7cac9316 XL |
502 | Now when we run the program, we won’t get any errors and we’ll see the |
503 | following output: | |
cc61c64b | 504 | |
3b2f2976 | 505 | ``` |
ea8adc8c | 506 | rect1 is Rectangle { width: 30, height: 50 } |
cc61c64b XL |
507 | ``` |
508 | ||
509 | Nice! It’s not the prettiest output, but it shows the values of all the fields | |
7cac9316 XL |
510 | for this instance, which would definitely help during debugging. When we have |
511 | larger structs, it’s useful to have output that’s a bit easier to read; in | |
512 | those cases, we can use `{:#?}` instead of `{:?}` in the `println!` string. | |
513 | When we use the `{:#?}` style in the example, the output will look like this: | |
cc61c64b | 514 | |
3b2f2976 | 515 | ``` |
cc61c64b | 516 | rect1 is Rectangle { |
ea8adc8c XL |
517 | width: 30, |
518 | height: 50 | |
cc61c64b XL |
519 | } |
520 | ``` | |
521 | ||
7cac9316 XL |
522 | Rust has provided a number of traits for us to use with the `derive` annotation |
523 | that can add useful behavior to our custom types. Those traits and their | |
524 | behaviors are listed in Appendix C. We’ll cover how to implement these traits | |
525 | with custom behavior as well as how to create your own traits in Chapter 10. | |
cc61c64b | 526 | |
7cac9316 XL |
527 | Our `area` function is very specific: it only computes the area of rectangles. |
528 | It would be helpful to tie this behavior more closely to our `Rectangle` | |
3b2f2976 | 529 | struct, because it won’t work with any other type. Let’s look at how we can |
7cac9316 XL |
530 | continue to refactor this code by turning the `area` function into an `area` |
531 | *method* defined on our `Rectangle` type. | |
cc61c64b XL |
532 | |
533 | ## Method Syntax | |
534 | ||
535 | *Methods* are similar to functions: they’re declared with the `fn` keyword and | |
041b39d2 | 536 | their name, they can have parameters and a return value, and they contain some |
7cac9316 XL |
537 | code that is run when they’re called from somewhere else. However, methods are |
538 | different from functions in that they’re defined within the context of a struct | |
539 | (or an enum or a trait object, which we cover in Chapters 6 and 17, | |
540 | respectively), and their first parameter is always `self`, which represents the | |
541 | instance of the struct the method is being called on. | |
cc61c64b XL |
542 | |
543 | ### Defining Methods | |
544 | ||
7cac9316 XL |
545 | Let’s change the `area` function that has a `Rectangle` instance as a parameter |
546 | and instead make an `area` method defined on the `Rectangle` struct, as shown | |
041b39d2 | 547 | in Listing 5-13: |
cc61c64b XL |
548 | |
549 | Filename: src/main.rs | |
550 | ||
3b2f2976 | 551 | ``` |
cc61c64b XL |
552 | #[derive(Debug)] |
553 | struct Rectangle { | |
cc61c64b | 554 | width: u32, |
ea8adc8c | 555 | height: u32, |
cc61c64b XL |
556 | } |
557 | ||
558 | impl Rectangle { | |
559 | fn area(&self) -> u32 { | |
ea8adc8c | 560 | self.width * self.height |
cc61c64b XL |
561 | } |
562 | } | |
563 | ||
564 | fn main() { | |
ea8adc8c | 565 | let rect1 = Rectangle { width: 30, height: 50 }; |
cc61c64b XL |
566 | |
567 | println!( | |
568 | "The area of the rectangle is {} square pixels.", | |
569 | rect1.area() | |
570 | ); | |
571 | } | |
572 | ``` | |
573 | ||
041b39d2 | 574 | Listing 5-13: Defining an `area` method on the `Rectangle` struct |
cc61c64b | 575 | |
7cac9316 XL |
576 | To define the function within the context of `Rectangle`, we start an `impl` |
577 | (*implementation*) block. Then we move the `area` function within the `impl` | |
578 | curly braces and change the first (and in this case, only) parameter to be | |
579 | `self` in the signature and everywhere within the body. In `main` where we | |
580 | called the `area` function and passed `rect1` as an argument, we can instead | |
581 | use *method syntax* to call the `area` method on our `Rectangle` instance. | |
582 | The method syntax goes after an instance: we add a dot followed by the method | |
583 | name, parentheses, and any arguments. | |
cc61c64b | 584 | |
7cac9316 XL |
585 | In the signature for `area`, we use `&self` instead of `rectangle: &Rectangle` |
586 | because Rust knows the type of `self` is `Rectangle` due to this method being | |
587 | inside the `impl Rectangle` context. Note that we still need to use the `&` | |
588 | before `self`, just like we did in `&Rectangle`. Methods can take ownership of | |
589 | `self`, borrow `self` immutably as we’ve done here, or borrow `self` mutably, | |
590 | just like any other parameter. | |
cc61c64b XL |
591 | |
592 | We’ve chosen `&self` here for the same reason we used `&Rectangle` in the | |
7cac9316 XL |
593 | function version: we don’t want to take ownership, and we just want to read the |
594 | data in the struct, not write to it. If we wanted to change the instance that | |
595 | we’ve called the method on as part of what the method does, we’d use `&mut | |
596 | self` as the first parameter. Having a method that takes ownership of the | |
597 | instance by using just `self` as the first parameter is rare; this technique is | |
598 | usually used when the method transforms `self` into something else and we want | |
599 | to prevent the caller from using the original instance after the transformation. | |
600 | ||
601 | The main benefit of using methods instead of functions, in addition to using | |
cc61c64b XL |
602 | method syntax and not having to repeat the type of `self` in every method’s |
603 | signature, is for organization. We’ve put all the things we can do with an | |
7cac9316 XL |
604 | instance of a type in one `impl` block rather than making future users of our |
605 | code search for capabilities of `Rectangle` in various places in the library we | |
606 | provide. | |
cc61c64b XL |
607 | |
608 | PROD: START BOX | |
609 | ||
610 | ### Where’s the `->` Operator? | |
611 | ||
7cac9316 XL |
612 | In languages like C++, two different operators are used for calling methods: |
613 | you use `.` if you’re calling a method on the object directly and `->` if | |
614 | you’re calling the method on a pointer to the object and need to dereference | |
615 | the pointer first. In other words, if `object` is a pointer, | |
616 | `object->something()` is similar to `(*object).something()`. | |
cc61c64b XL |
617 | |
618 | Rust doesn’t have an equivalent to the `->` operator; instead, Rust has a | |
619 | feature called *automatic referencing and dereferencing*. Calling methods is | |
7cac9316 | 620 | one of the few places in Rust that has this behavior. |
cc61c64b XL |
621 | |
622 | Here’s how it works: when you call a method with `object.something()`, Rust | |
7cac9316 XL |
623 | automatically adds in `&`, `&mut`, or `*` so `object` matches the signature of |
624 | the method. In other words, the following are the same: | |
cc61c64b | 625 | |
3b2f2976 | 626 | ``` |
cc61c64b XL |
627 | p1.distance(&p2); |
628 | (&p1).distance(&p2); | |
629 | ``` | |
630 | ||
7cac9316 XL |
631 | The first one looks much cleaner. This automatic referencing behavior works |
632 | because methods have a clear receiver—the type of `self`. Given the receiver | |
633 | and name of a method, Rust can figure out definitively whether the method is | |
3b2f2976 XL |
634 | reading (`&self`), mutating (`&mut self`), or consuming (`self`). The fact |
635 | that Rust makes borrowing implicit for method receivers is a big part of | |
636 | making ownership ergonomic in practice. | |
cc61c64b XL |
637 | |
638 | PROD: END BOX | |
639 | ||
7cac9316 | 640 | ### Methods with More Parameters |
cc61c64b | 641 | |
7cac9316 XL |
642 | Let’s practice using methods by implementing a second method on the `Rectangle` |
643 | struct. This time, we want an instance of `Rectangle` to take another instance | |
644 | of `Rectangle` and return `true` if the second `Rectangle` can fit completely | |
645 | within `self`; otherwise it should return `false`. That is, we want to be able | |
041b39d2 | 646 | to write the program shown in Listing 5-14, once we’ve defined the `can_hold` |
7cac9316 | 647 | method: |
cc61c64b XL |
648 | |
649 | Filename: src/main.rs | |
650 | ||
3b2f2976 | 651 | ``` |
cc61c64b | 652 | fn main() { |
ea8adc8c XL |
653 | let rect1 = Rectangle { width: 30, height: 50 }; |
654 | let rect2 = Rectangle { width: 10, height: 40 }; | |
655 | let rect3 = Rectangle { width: 60, height: 45 }; | |
cc61c64b XL |
656 | |
657 | println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2)); | |
658 | println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3)); | |
659 | } | |
660 | ``` | |
661 | ||
041b39d2 | 662 | Listing 5-14: Demonstration of using the as-yet-unwritten `can_hold` method |
cc61c64b | 663 | |
7cac9316 XL |
664 | And the expected output would look like the following, because both dimensions |
665 | of `rect2` are smaller than the dimensions of `rect1`, but `rect3` is wider | |
666 | than `rect1`: | |
cc61c64b | 667 | |
3b2f2976 | 668 | ``` |
cc61c64b XL |
669 | Can rect1 hold rect2? true |
670 | Can rect1 hold rect3? false | |
671 | ``` | |
672 | ||
673 | We know we want to define a method, so it will be within the `impl Rectangle` | |
674 | block. The method name will be `can_hold`, and it will take an immutable borrow | |
7cac9316 XL |
675 | of another `Rectangle` as a parameter. We can tell what the type of the |
676 | parameter will be by looking at the code that calls the method: | |
677 | `rect1.can_hold(&rect2)` passes in `&rect2`, which is an immutable borrow to | |
678 | `rect2`, an instance of `Rectangle`. This makes sense because we only need to | |
679 | read `rect2` (rather than write, which would mean we’d need a mutable borrow), | |
680 | and we want `main` to retain ownership of `rect2` so we can use it again after | |
681 | calling the `can_hold` method. The return value of `can_hold` will be a | |
ea8adc8c XL |
682 | boolean, and the implementation will check whether the width and height of |
683 | `self` are both greater than the width and height of the other `Rectangle`, | |
7cac9316 | 684 | respectively. Let’s add the new `can_hold` method to the `impl` block from |
041b39d2 | 685 | Listing 5-13, shown in Listing 5-15: |
cc61c64b XL |
686 | |
687 | Filename: src/main.rs | |
688 | ||
3b2f2976 | 689 | ``` |
cc61c64b XL |
690 | impl Rectangle { |
691 | fn area(&self) -> u32 { | |
ea8adc8c | 692 | self.width * self.height |
cc61c64b XL |
693 | } |
694 | ||
695 | fn can_hold(&self, other: &Rectangle) -> bool { | |
ea8adc8c | 696 | self.width > other.width && self.height > other.height |
cc61c64b XL |
697 | } |
698 | } | |
699 | ``` | |
700 | ||
041b39d2 | 701 | Listing 5-15: Implementing the `can_hold` method on `Rectangle` that takes |
7cac9316 | 702 | another `Rectangle` instance as a parameter |
cc61c64b | 703 | |
041b39d2 | 704 | When we run this code with the `main` function in Listing 5-14, we’ll get our |
7cac9316 XL |
705 | desired output. Methods can take multiple parameters that we add to the |
706 | signature after the `self` parameter, and those parameters work just like | |
707 | parameters in functions. | |
cc61c64b XL |
708 | |
709 | ### Associated Functions | |
710 | ||
7cac9316 XL |
711 | Another useful feature of `impl` blocks is that we’re allowed to define |
712 | functions within `impl` blocks that *don’t* take `self` as a parameter. These | |
713 | are called *associated functions* because they’re associated with the struct. | |
714 | They’re still functions, not methods, because they don’t have an instance of | |
715 | the struct to work with. You’ve already used the `String::from` associated | |
716 | function. | |
cc61c64b XL |
717 | |
718 | Associated functions are often used for constructors that will return a new | |
719 | instance of the struct. For example, we could provide an associated function | |
ea8adc8c | 720 | that would have one dimension parameter and use that as both width and height, |
cc61c64b XL |
721 | thus making it easier to create a square `Rectangle` rather than having to |
722 | specify the same value twice: | |
723 | ||
724 | Filename: src/main.rs | |
725 | ||
3b2f2976 | 726 | ``` |
cc61c64b XL |
727 | impl Rectangle { |
728 | fn square(size: u32) -> Rectangle { | |
ea8adc8c | 729 | Rectangle { width: size, height: size } |
cc61c64b XL |
730 | } |
731 | } | |
732 | ``` | |
733 | ||
7cac9316 | 734 | To call this associated function, we use the `::` syntax with the struct name, |
3b2f2976 XL |
735 | like `let sq = Rectangle::square(3);`, for example. This function is |
736 | namespaced by the struct: the `::` syntax is used for both associated functions | |
737 | and namespaces created by modules, which we’ll discuss in Chapter 7. | |
738 | ||
739 | ### Multiple `impl` Blocks | |
740 | ||
741 | Each struct is allowed to have multiple `impl` blocks. For example, Listing | |
742 | 5-15 is equivalent to the code shown in Listing 5-16, which has each method | |
743 | in its own `impl` block: | |
744 | ||
745 | ``` | |
746 | impl Rectangle { | |
747 | fn area(&self) -> u32 { | |
ea8adc8c | 748 | self.width * self.height |
3b2f2976 XL |
749 | } |
750 | } | |
751 | ||
752 | impl Rectangle { | |
753 | fn can_hold(&self, other: &Rectangle) -> bool { | |
ea8adc8c | 754 | self.width > other.width && self.height > other.height |
3b2f2976 XL |
755 | } |
756 | } | |
757 | ``` | |
758 | ||
759 | Listing 5-16: Rewriting Listing 5-15 using multiple `impl` blocks | |
760 | ||
761 | There’s no reason to separate these methods into multiple `impl` blocks here, | |
762 | but it’s valid syntax. We will see a case when multiple `impl` blocks are useful | |
763 | in Chapter 10 when we discuss generic types and traits. | |
cc61c64b XL |
764 | |
765 | ## Summary | |
766 | ||
767 | Structs let us create custom types that are meaningful for our domain. By using | |
768 | structs, we can keep associated pieces of data connected to each other and name | |
769 | each piece to make our code clear. Methods let us specify the behavior that | |
770 | instances of our structs have, and associated functions let us namespace | |
771 | functionality that is particular to our struct without having an instance | |
772 | available. | |
773 | ||
7cac9316 XL |
774 | But structs aren’t the only way we can create custom types: let’s turn to |
775 | Rust’s enum feature to add another tool to our toolbox. |