blob: a6ea2bb7e9e50cc64f72b5b3fe17689786d54b5c [file] [log] [blame] [view] [edit]
## An Example Program Using Structs
To understand when we might want to use structs, lets write a program that
calculates the area of a rectangle. Well start by using single variables, and
then refactor the program until were using structs instead.
Lets make a new binary project with Cargo called _rectangles_ that will take
the width and height of a rectangle specified in pixels and calculate the area
of the rectangle. Listing 5-8 shows a short program with one way of doing
exactly that in our projects _src/main.rs_.
<Listing number="5-8" file-name="src/main.rs" caption="Calculating the area of a rectangle specified by separate width and height variables">
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-08/src/main.rs:all}}
```
</Listing>
Now, run this program using `cargo run`:
```console
{{#include ../listings/ch05-using-structs-to-structure-related-data/listing-05-08/output.txt}}
```
This code succeeds in figuring out the area of the rectangle by calling the
`area` function with each dimension, but we can do more to make this code clear
and readable.
The issue with this code is evident in the signature of `area`:
```rust,ignore
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-08/src/main.rs:here}}
```
The `area` function is supposed to calculate the area of one rectangle, but the
function we wrote has two parameters, and its not clear anywhere in our program
that the parameters are related. It would be more readable and more manageable
to group width and height together. Weve already discussed one way we might do
that in [“The Tuple Type”][the-tuple-type]<!-- ignore --> section of Chapter 3:
by using tuples.
### Refactoring with Tuples
Listing 5-9 shows another version of our program that uses tuples.
<Listing number="5-9" file-name="src/main.rs" caption="Specifying the width and height of the rectangle with a tuple">
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-09/src/main.rs}}
```
</Listing>
In one way, this program is better. Tuples let us add a bit of structure, and
were now passing just one argument. But in another way, this version is less
clear: tuples dont name their elements, so we have to index into the parts of
the tuple, making our calculation less obvious.
Mixing up the width and height wouldnt matter for the area calculation, but if
we want to draw the rectangle on the screen, it would matter! We would have to
keep in mind that `width` is the tuple index `0` and `height` is the tuple index
`1`. This would be even harder for someone else to figure out and keep in mind
if they were to use our code. Because we havent conveyed the meaning of our
data in our code, its now easier to introduce errors.
### Refactoring with Structs: Adding More Meaning
We use structs to add meaning by labeling the data. We can transform the tuple
were using into a struct with a name for the whole as well as names for the
parts, as shown in Listing 5-10.
<Listing number="5-10" file-name="src/main.rs" caption="Defining a `Rectangle` struct">
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-10/src/main.rs}}
```
</Listing>
Here weve defined a struct and named it `Rectangle`. Inside the curly brackets,
we defined the fields as `width` and `height`, both of which have type `u32`.
Then, in `main`, we created a particular instance of `Rectangle` that has a
width of `30` and a height of `50`.
Our `area` function is now defined with one parameter, which weve named
`rectangle`, whose type is an immutable borrow of a struct `Rectangle` instance.
As mentioned in Chapter 4, we want to borrow the struct rather than take
ownership of it. This way, `main` retains its ownership and can continue using
`rect1`, which is the reason we use the `&` in the function signature and where
we call the function.
The `area` function accesses the `width` and `height` fields of the `Rectangle`
instance (note that accessing fields of a borrowed struct instance does not move
the field values, which is why you often see borrows of structs). Our function
signature for `area` now says exactly what we mean: calculate the area of
`Rectangle`, using its `width` and `height` fields. This conveys that the width
and height are related to each other, and it gives descriptive names to the
values rather than using the tuple index values of `0` and `1`. This is a win
for clarity.
### Adding Useful Functionality with Derived Traits
Itd be useful to be able to print an instance of `Rectangle` while were
debugging our program and see the values for all its fields. Listing 5-11 tries
using the [`println!` macro][println]<!-- ignore --> as we have used in previous
chapters. This wont work, however.
<Listing number="5-11" file-name="src/main.rs" caption="Attempting to print a `Rectangle` instance">
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-11/src/main.rs}}
```
</Listing>
When we compile this code, we get an error with this core message:
```text
{{#include ../listings/ch05-using-structs-to-structure-related-data/listing-05-11/output.txt:3}}
```
The `println!` macro can do many kinds of formatting, and by default, the curly
brackets tell `println!` to use formatting known as `Display`: output intended
for direct end user consumption. The primitive types weve seen so far implement
`Display` by default because theres only one way youd want to show a `1` or
any other primitive type to a user. But with structs, the way `println!` should
format the output is less clear because there are more display possibilities: Do
you want commas or not? Do you want to print the curly brackets? Should all the
fields be shown? Due to this ambiguity, Rust doesnt try to guess what we want,
and structs dont have a provided implementation of `Display` to use with
`println!` and the `{}` placeholder.
If we continue reading the errors, well find this helpful note:
```text
{{#include ../listings/ch05-using-structs-to-structure-related-data/listing-05-11/output.txt:9:10}}
```
Lets try it! The `println!` macro call will now look like
`println!("rect1 is
{rect1:?}");`. Putting the specifier `:?` inside the curly
brackets tells `println!` we want to use an output format called `Debug`. The
`Debug` trait enables us to print our struct in a way that is useful for
developers so we can see its value while were debugging our code.
Compile the code with this change. Drat! We still get an error:
```text
{{#include ../listings/ch05-using-structs-to-structure-related-data/output-only-01-debug/output.txt:3}}
```
But again, the compiler gives us a helpful note:
```text
{{#include ../listings/ch05-using-structs-to-structure-related-data/output-only-01-debug/output.txt:9:10}}
```
Rust _does_ include functionality to print out debugging information, but we
have to explicitly opt in to make that functionality available for our struct.
To do that, we add the outer attribute `#[derive(Debug)]` just before the struct
definition, as shown in Listing 5-12.
<Listing number="5-12" file-name="src/main.rs" caption="Adding the attribute to derive the `Debug` trait and printing the `Rectangle` instance using debug formatting">
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-12/src/main.rs}}
```
</Listing>
Now when we run the program, we wont get any errors, and well see the
following output:
```console
{{#include ../listings/ch05-using-structs-to-structure-related-data/listing-05-12/output.txt}}
```
Nice! Its not the prettiest output, but it shows the values of all the fields
for this instance, which would definitely help during debugging. When we have
larger structs, its useful to have output thats a bit easier to read; in those
cases, we can use `{:#?}` instead of `{:?}` in the `println!` string. In this
example, using the `{:#?}` style will output the following:
```console
{{#include ../listings/ch05-using-structs-to-structure-related-data/output-only-02-pretty-debug/output.txt}}
```
Another way to print out a value using the `Debug` format is to use the
[`dbg!` macro][dbg]<!-- ignore -->, which takes ownership of an expression (as
opposed to `println!`, which takes a reference), prints the file and line number
of where that `dbg!` macro call occurs in your code along with the resultant
value of that expression, and returns ownership of the value.
> Note: Calling the `dbg!` macro prints to the standard error console stream
> (`stderr`), as opposed to `println!`, which prints to the standard output
> console stream (`stdout`). Well talk more about `stderr` and `stdout` in the
> [“Writing Error Messages to Standard Error Instead of Standard Output
> section in Chapter 12][err]<!-- ignore -->.
Heres an example where were interested in the value that gets assigned to the
`width` field, as well as the value of the whole struct in `rect1`:
```rust
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-05-dbg-macro/src/main.rs}}
```
We can put `dbg!` around the expression `30 * scale` and, because `dbg!` returns
ownership of the expressions value, the `width` field will get the same value
as if we didnt have the `dbg!` call there. We dont want `dbg!` to take
ownership of `rect1`, so we use a reference to `rect1` in the next call. Heres
what the output of this example looks like:
```console
{{#include ../listings/ch05-using-structs-to-structure-related-data/no-listing-05-dbg-macro/output.txt}}
```
We can see the first bit of output came from _src/main.rs_ line 10 where were
debugging the expression `30 * scale`, and its resultant value is `60` (the
`Debug` formatting implemented for integers is to print only their value). The
`dbg!` call on line 14 of _src/main.rs_ outputs the value of `&rect1`, which is
the `Rectangle` struct. This output uses the pretty `Debug` formatting of the
`Rectangle` type. The `dbg!` macro can be really helpful when youre trying to
figure out what your code is doing!
In addition to the `Debug` trait, Rust has provided a number of traits for us to
use with the `derive` attribute that can add useful behavior to our custom
types. Those traits and their behaviors are listed in [Appendix C][app-c]<!--
ignore -->. Well cover how to implement these traits with custom behavior as
well as how to create your own traits in Chapter 10. There are also many
attributes other than `derive`; for more information, see
[the Attributes section of the Rust Reference][attributes].
Our `area` function is very specific: it only computes the area of rectangles.
It would be helpful to tie this behavior more closely to our `Rectangle` struct
because it wont work with any other type. Lets look at how we can continue to
refactor this code by turning the `area` function into an `area` _method_
defined on our `Rectangle` type.
[the-tuple-type]: ch03-02-data-types.html#the-tuple-type
[app-c]: appendix-03-derivable-traits.md
[println]: ../std/macro.println.html
[dbg]: ../std/macro.dbg.html
[err]: ch12-06-writing-to-stderr-instead-of-stdout.html
[attributes]: ../reference/attributes.html