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