mirror of
https://github.com/rust-lang/book.git
synced 2026-09-15 08:59:17 -04:00
Edits to chapter 5 after copy editing review
This commit is contained in:
@@ -1,28 +1,28 @@
|
||||
# Structs
|
||||
# Using Structs to Structure Related Data
|
||||
|
||||
A `struct`, short for *structure*, is a custom data type that lets us name and
|
||||
package together multiple related values that make up a meaningful group. If
|
||||
you come from an object-oriented language, a `struct` is like an object’s data
|
||||
attributes. In the next section of this chapter, we’ll talk about how to define
|
||||
methods on our structs; methods are how you specify the *behavior* that goes
|
||||
along with a struct’s data. The `struct` and `enum` (that we will talk about in
|
||||
Chapter 6) concepts are the building blocks for creating new types in your
|
||||
program’s domain in order to take full advantage of Rust’s compile-time type
|
||||
checking.
|
||||
A *struct*, or *structure*, is a custom data type that lets us name and package
|
||||
together multiple related values that make up a meaningful group. If you’re
|
||||
familiar with an object-oriented language, a *struct* is like an object’s data
|
||||
attributes. In this chapter, we'll compare and contrast tuples with structs,
|
||||
demonstrate how to use structs, and discuss how to define methods and
|
||||
associated functions on structs to specify behavior associated with a struct's
|
||||
data . The struct and *enum* (which is discussed in Chapter 6) concepts are the
|
||||
building blocks for creating new types in your program’s domain to take full
|
||||
advantage of Rust’s compile time type checking.
|
||||
|
||||
One way of thinking about structs is that they are similar to tuples, which we
|
||||
talked about in Chapter 3. Like tuples, the pieces of a struct can be different
|
||||
types. Unlike tuples, we name each piece of data so that it’s clearer what the
|
||||
values mean. Structs are more flexible as a result of these names: we don’t
|
||||
have to rely on the order of the data to specify or access the values of an
|
||||
instance.
|
||||
## Defining and Instantiating Structs
|
||||
|
||||
To define a struct, we enter the keyword `struct` and give the whole struct a
|
||||
name. A struct’s name should describe what the significance is of these pieces
|
||||
of data being grouped together. Then, inside curly braces, we define the names
|
||||
of the pieces of data, which we call *fields*, and specify each field’s type.
|
||||
For example, Listing 5-1 shows a struct to store information about a user
|
||||
account:
|
||||
Structs are similar to tuples, which were discussed in Chapter 3. Like tuples,
|
||||
the pieces of a struct can be different types. Unlike tuples, we name each
|
||||
piece of data so it’s clear what the values mean. As a result of these names,
|
||||
structs are more flexible than tuples: we don’t have to rely on the order of
|
||||
the data to specify or access the values of an instance.
|
||||
|
||||
To define a struct, we enter the keyword `struct` and name the entire struct. A
|
||||
struct’s name should describe the significance of the pieces of data being
|
||||
grouped together. Then, inside curly braces, we define the names and types of
|
||||
the pieces of data, which we call *fields*. For example, Listing 5-1 shows a
|
||||
struct to store information about a user account:
|
||||
|
||||
```rust
|
||||
struct User {
|
||||
@@ -35,14 +35,14 @@ struct User {
|
||||
|
||||
<span class="caption">Listing 5-1: A `User` struct definition</span>
|
||||
|
||||
To use a struct once we've defined it, we create an *instance* of that struct
|
||||
by specifying concrete values for each of the fields. Creating an instance is
|
||||
done by stating the name of the struct, then curly braces with `key: value`
|
||||
pairs inside it where the keys are the names of the fields and the values are
|
||||
the data we want to store in those fields. The fields don’t have to be
|
||||
specified in the same order in which the struct declared them. In other words,
|
||||
the struct definition is like a general template for the type, and instances
|
||||
fill in that template with particular data to create values of the type. For
|
||||
To use a struct after we’ve defined it, we create an *instance* of that struct
|
||||
by specifying concrete values for each of the fields. We create an instance by
|
||||
stating the name of the struct, and then add curly braces containing `key:
|
||||
value` pairs where the keys are the names of the fields and the values are the
|
||||
data we want to store in those fields. We don’t have to specify the fields in
|
||||
the same order in which we declared them in the struct. In other words, the
|
||||
struct definition is like a general template for the type, and instances fill
|
||||
in that template with particular data to create values of the type. For
|
||||
example, we can declare a particular user like this:
|
||||
|
||||
```rust
|
||||
@@ -61,71 +61,72 @@ let user1 = User {
|
||||
};
|
||||
```
|
||||
|
||||
To get a particular value out of a struct, we can use dot notation. If we
|
||||
wanted just this user’s email address, we can say `user1.email`.
|
||||
To get a specific value from a struct, we can use dot notation. If we wanted
|
||||
just this user’s email address, we can use `user1.email` wherever we want to
|
||||
use this value.
|
||||
|
||||
## Ownership of Struct Data
|
||||
> ### Ownership of Struct Data
|
||||
>
|
||||
> In the `User` struct definition in Listing 5-1, we used the owned `String`
|
||||
> type rather than the `&str` string slice type. This is a deliberate choice
|
||||
> because we want instances of this struct to own all of its data and for that
|
||||
> data to be valid for as long as the entire struct is valid.
|
||||
>
|
||||
> It’s possible for structs to store references to data owned by something else,
|
||||
> but to do so requires the use of *lifetimes*, a Rust feature that is discussed
|
||||
> in Chapter 10. Lifetimes ensure that the data referenced by a struct is valid
|
||||
> for as long as the struct is. Let’s say you try to store a reference in a
|
||||
> struct without specifying lifetimes, like this:
|
||||
>
|
||||
> <span class="filename">Filename: src/main.rs</span>
|
||||
>
|
||||
> ```rust,ignore
|
||||
> struct User {
|
||||
> username: &str,
|
||||
> email: &str,
|
||||
> sign_in_count: u64,
|
||||
> active: bool,
|
||||
> }
|
||||
>
|
||||
> fn main() {
|
||||
> let user1 = User {
|
||||
> email: "someone@example.com",
|
||||
> username: "someusername123",
|
||||
> active: true,
|
||||
> sign_in_count: 1,
|
||||
> };
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> The compiler will complain that it needs lifetime specifiers:
|
||||
>
|
||||
> ```text
|
||||
> error[E0106]: missing lifetime specifier
|
||||
> -->
|
||||
> |
|
||||
> 2 | username: &str,
|
||||
> | ^ expected lifetime parameter
|
||||
>
|
||||
> error[E0106]: missing lifetime specifier
|
||||
> -->
|
||||
> |
|
||||
> 3 | email: &str,
|
||||
> | ^ expected lifetime parameter
|
||||
> ```
|
||||
>
|
||||
> We’ll discuss how to fix these errors so you can store references in structs
|
||||
> in Chapter 10, but for now, we’ll fix errors like these using owned types like
|
||||
> `String` instead of references like `&str`.
|
||||
|
||||
In the `User` struct definition in Listing 5-1, we used the owned `String` type
|
||||
rather than the `&str` string slice type. This is a deliberate choice because
|
||||
we want instances of this struct to own all of its data, and for that data to
|
||||
be valid for as long as the entire struct is valid.
|
||||
|
||||
It is possible for structs to store references to data owned by something else,
|
||||
but to do so requires the use of *lifetimes*, a feature of Rust that we'll
|
||||
discuss in Chapter 10. Lifetimes ensure that the data a struct references is
|
||||
valid for as long as the struct is. If you try to store a reference in a struct
|
||||
without specifying lifetimes, like this:
|
||||
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
```rust,ignore
|
||||
struct User {
|
||||
username: &str,
|
||||
email: &str,
|
||||
sign_in_count: u64,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let user1 = User {
|
||||
email: "someone@example.com",
|
||||
username: "someusername123",
|
||||
active: true,
|
||||
sign_in_count: 1,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
The compiler will complain that it needs lifetime specifiers:
|
||||
|
||||
```text
|
||||
error[E0106]: missing lifetime specifier
|
||||
-->
|
||||
|
|
||||
2 | username: &str,
|
||||
| ^ expected lifetime parameter
|
||||
|
||||
error[E0106]: missing lifetime specifier
|
||||
-->
|
||||
|
|
||||
3 | email: &str,
|
||||
| ^ expected lifetime parameter
|
||||
```
|
||||
|
||||
We will talk about how to fix these errors in order to store references in
|
||||
structs in Chapter 10, but for now, fix errors like these by switching to owned
|
||||
types like `String` instead of references like `&str`.
|
||||
|
||||
## An Example Program
|
||||
## An Example Program Using Structs
|
||||
|
||||
To understand when we might want to use structs, let’s write a program that
|
||||
calculates the area of a rectangle. We’ll start off with single variables, then
|
||||
refactor our program until we’re using structs instead.
|
||||
calculates the area of a rectangle. We’ll start with single variables, and then
|
||||
refactor the program until we’re using structs instead.
|
||||
|
||||
Let’s make a new binary project with Cargo called *rectangles* that will take
|
||||
the length and width of a rectangle specified in pixels and will calculate the
|
||||
area of the rectangle. Listing 5-2 has a short program with one way of doing
|
||||
area of the rectangle. Listing 5-2 shows a short program with one way of doing
|
||||
just that in our project’s *src/main.rs*:
|
||||
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
@@ -149,7 +150,7 @@ fn area(length: u32, width: u32) -> u32 {
|
||||
<span class="caption">Listing 5-2: Calculating the area of a rectangle
|
||||
specified by its length and width in separate variables</span>
|
||||
|
||||
Let’s try running this program with `cargo run`:
|
||||
Now, run this program using `cargo run`:
|
||||
|
||||
```text
|
||||
The area of the rectangle is 1500 square pixels.
|
||||
@@ -157,9 +158,9 @@ The area of the rectangle is 1500 square pixels.
|
||||
|
||||
### Refactoring with Tuples
|
||||
|
||||
Our little program works okay; it figures out the area of the rectangle by
|
||||
calling the `area` function with each dimension. But we can do better. The
|
||||
length and the width are related to each other since together they describe one
|
||||
Even though Listing 5-2 works and figures out the area of the rectangle by
|
||||
calling the `area` function with each dimension, we can do better. The length
|
||||
and the width are related to each other because together they describe one
|
||||
rectangle.
|
||||
|
||||
The issue with this method is evident in the signature of `area`:
|
||||
@@ -168,13 +169,14 @@ The issue with this method is evident in the signature of `area`:
|
||||
fn area(length: u32, width: u32) -> u32 {
|
||||
```
|
||||
|
||||
The `area` function is supposed to calculate the area of one rectangle, but our
|
||||
function has two parameters. The parameters are related, but that’s not
|
||||
expressed anywhere in our program itself. It would be more readable and more
|
||||
The `area` function is supposed to calculate the area of one rectangle, but the
|
||||
function we wrote has two parameters. The parameters are related, but that’s
|
||||
not expressed anywhere in our program. It would be more readable and more
|
||||
manageable to group length and width together.
|
||||
|
||||
We’ve already discussed one way we might do that in Chapter 3: tuples. Listing
|
||||
5-3 has a version of our program which uses tuples:
|
||||
We’ve already discussed one way we might do that in the Grouping Values into
|
||||
Tuples section of Chapter 3 on page XX: by using tuples. Listing 5-3 shows
|
||||
another version of our program that uses tuples:
|
||||
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
@@ -196,34 +198,24 @@ fn area(dimensions: (u32, u32)) -> u32 {
|
||||
<span class="caption">Listing 5-3: Specifying the length and width of the
|
||||
rectangle with a tuple</span>
|
||||
|
||||
<!-- I will add ghosting & wingdings once we're in libreoffice /Carol -->
|
||||
|
||||
In one way, this is a little better. Tuples let us add a bit of structure, and
|
||||
we’re now passing just one argument when we call `area`. But in another way
|
||||
this method is less clear: tuples don’t give names to their elements, so our
|
||||
calculation has gotten more confusing because we have to index into the parts
|
||||
of the tuple:
|
||||
|
||||
<!-- I will change this to use wingdings instead of repeating this code once
|
||||
we're in libreoffice /Carol -->
|
||||
|
||||
```rust,ignore
|
||||
dimensions.0 * dimensions.1
|
||||
```
|
||||
In one way, this program is better. Tuples let us add a bit of structure, and
|
||||
we’re now passing just one argument. But in another way this version is less
|
||||
clear: tuples don’t name their elements, so our calculation has become more
|
||||
confusing because we have to index into the parts of the tuple.
|
||||
|
||||
It doesn’t matter if we mix up length and width for the area calculation, but
|
||||
if we were to draw the rectangle on the screen it would matter! We would have
|
||||
to remember that `length` was the tuple index `0` and `width` was the tuple
|
||||
index `1`. If someone else was to work on this code, they would have to figure
|
||||
this out and remember it as well. It would be easy to forget or mix these
|
||||
values up and cause errors, since we haven’t conveyed the meaning of our data
|
||||
in our code.
|
||||
if we want to draw the rectangle on the screen, it would matter! We would have
|
||||
to keep in mind that `length` is the tuple index `0` and `width` is the tuple
|
||||
index `1`. If someone else worked on this code, they would have to figure this
|
||||
out and keep it in mind as well. It would be easy to forget or mix up these
|
||||
values and cause errors, because we haven’t conveyed the meaning of our data in
|
||||
our code.
|
||||
|
||||
### Refactoring with Structs: Adding More Meaning
|
||||
|
||||
Here is where we bring in structs. We can transform our tuple into a data type
|
||||
with a name for the whole as well as names for the parts, as shown in Listing
|
||||
5-4:
|
||||
We use structs to add meaning by labeling the data. We can transform the tuple
|
||||
we’re using into a data type with a name for the whole as well as names for the
|
||||
parts, as shown in Listing 5-4:
|
||||
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
@@ -249,32 +241,31 @@ fn area(rectangle: &Rectangle) -> u32 {
|
||||
|
||||
<span class="caption">Listing 5-4: Defining a `Rectangle` struct</span>
|
||||
|
||||
<!-- Will add ghosting & wingdings once we're in libreoffice /Carol -->
|
||||
Here we’ve defined a struct and named it `Rectangle`. Inside the `{}` we
|
||||
defined the fields as `length` and `width`, both of which have type `u32`. Then
|
||||
in `main` we create a particular instance of a `Rectangle` that has a length of
|
||||
50 and a width of 30.
|
||||
|
||||
Here we’ve defined a struct and given it the name `Rectangle`. Inside the `{}`
|
||||
we defined the fields to be `length` and `width`, both of which have type
|
||||
`u32`. Then in `main`, we create a particular instance of a `Rectangle` that
|
||||
has a length of 50 and a width of 30.
|
||||
Our `area` function is now defined with one parameter, which we’ve 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.
|
||||
|
||||
Our `area` function is now defined with one parameter that we’ve named
|
||||
`rectangle` whose type is an immutable borrow of a struct `Rectangle` instance.
|
||||
As we covered in Chapter 4, we want to borrow the struct rather than take
|
||||
ownership of it so that `main` keeps its ownership and can continue using
|
||||
`rect1`, so that’s why we have the `&` in the function signature and at the
|
||||
call site.
|
||||
|
||||
The `area` function accesses the `length` and `width` fields of the
|
||||
`Rectangle`. Our function signature for `area` now says exactly what we mean:
|
||||
calculate the area of a `Rectangle`, using its `length` and `width` fields.
|
||||
This conveys that the length and width are related to each other, and gives
|
||||
The `area` function accesses the `length` and `width` fields of the `Rectangle`
|
||||
instance. Our function signature for `area` now indicates exactly what we mean:
|
||||
calculate the area of a `Rectangle` using its `length` and `width` fields. This
|
||||
conveys that the length and width are related to each other, and gives
|
||||
descriptive names to the values rather than using the tuple index values of `0`
|
||||
and `1`. This is a win for clarity.
|
||||
and `1`—a win for clarity.
|
||||
|
||||
### Adding Useful Functionality with Derived Traits
|
||||
|
||||
It’d be nice to be able to print out an instance of our `Rectangle` while we’re
|
||||
debugging our program and see the values for all its fields. Listing 5-5 tries
|
||||
using the `println!` macro as we have been:
|
||||
It would be helpful to be able to print out an instance of the `Rectangle`
|
||||
while we’re debugging our program in order to see the values for all its
|
||||
fields. Listing 5-5 uses the `println!` macro as we have been in earlier
|
||||
chapters:
|
||||
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
@@ -294,7 +285,7 @@ fn main() {
|
||||
<span class="caption">Listing 5-5: Attempting to print a `Rectangle`
|
||||
instance</span>
|
||||
|
||||
If we run this, we get an error with this core message:
|
||||
When we run this code, we get an error with this core message:
|
||||
|
||||
```text
|
||||
error[E0277]: the trait bound `Rectangle: std::fmt::Display` is not satisfied
|
||||
@@ -302,35 +293,34 @@ error[E0277]: the trait bound `Rectangle: std::fmt::Display` is not satisfied
|
||||
|
||||
The `println!` macro can do many kinds of formatting, and by default, `{}`
|
||||
tells `println!` to use formatting known as `Display`: output intended for
|
||||
direct end-user consumption. The primitive types we’ve seen so far implement
|
||||
`Display` by default, as there’s only one way you’d 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 as there are more display possibilities: Do you
|
||||
want commas or not? Do you want to print the struct `{}`s? Should all the
|
||||
fields be shown? Because of this ambiguity, Rust doesn’t try to guess what we
|
||||
want and structs do not have a provided implementation of `Display`.
|
||||
direct end user consumption. The primitive types we’ve seen so far implement
|
||||
`Display` by default, because there’s only one way you’d 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 braces? Should all
|
||||
the fields be shown? Due to this ambiguity, Rust doesn’t try to guess what we
|
||||
want and structs don’t have a provided implementation of `Display`.
|
||||
|
||||
If we keep reading the errors, though, we’ll find this helpful note:
|
||||
If we continue reading the errors, we’ll find this helpful note:
|
||||
|
||||
```text
|
||||
note: `Rectangle` cannot be formatted with the default formatter; try using
|
||||
`:?` instead if you are using a format string
|
||||
```
|
||||
|
||||
Let’s try it! The `println!` will now look like
|
||||
`println!("rect1 is {:?}", rect1);`. Putting the specifier `:?` inside
|
||||
the `{}` tells `println!` we want to use an output format called `Debug`.
|
||||
`Debug` is a trait that enables us to print out our struct in a way that is
|
||||
useful for developers so that we can see its value while we are debugging our
|
||||
code.
|
||||
Let’s try it! The `println!` macro call will now look like `println!("rect1 is
|
||||
{:?}", rect1);`. Putting the specifier `:?` inside the `{}` tells `println!` we
|
||||
want to use an output format called `Debug`. `Debug` is a trait that enables us
|
||||
to print out our struct in a way that is useful for developers so we can see
|
||||
its value while we’re debugging our code.
|
||||
|
||||
Let’s try running with this change and… drat. We still get an error:
|
||||
Run the code with this change. Drat! We still get an error:
|
||||
|
||||
```text
|
||||
error: the trait bound `Rectangle: std::fmt::Debug` is not satisfied
|
||||
```
|
||||
|
||||
Again, though, the compiler has given us a helpful note!
|
||||
But again, the compiler gives us a helpful note:
|
||||
|
||||
```text
|
||||
note: `Rectangle` cannot be formatted using `:?`; if it is defined in your
|
||||
@@ -338,9 +328,11 @@ crate, add `#[derive(Debug)]` or manually implement it
|
||||
```
|
||||
|
||||
Rust *does* include functionality to print out debugging information, but we
|
||||
have to explicitly opt-in to having that functionality be available for our
|
||||
struct. To do that, we add the annotation `#[derive(Debug)]` just before our
|
||||
struct definition, as shown in Listing 5-6:
|
||||
have to explicitly opt-in to make that functionality available for our struct.
|
||||
To do that, we add the annotation `#[derive(Debug)]` just before the struct
|
||||
definition, as shown in Listing 5-6:
|
||||
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
#[derive(Debug)]
|
||||
@@ -359,18 +351,18 @@ fn main() {
|
||||
<span class="caption">Listing 5-6: Adding the annotation to derive the `Debug`
|
||||
trait and printing the `Rectangle` instance using debug formatting</span>
|
||||
|
||||
At this point, if we run this program, we won’t get any errors and we’ll see
|
||||
the following output:
|
||||
Now when we run the program, we won’t get any errors and we’ll see the
|
||||
following output:
|
||||
|
||||
```text
|
||||
rect1 is Rectangle { length: 50, width: 30 }
|
||||
```
|
||||
|
||||
Nice! It’s not the prettiest output, but it shows the values of all the fields
|
||||
for this instance, which would definitely help during debugging. If we want
|
||||
output that is a bit prettier and easier to read, which can be helpful with
|
||||
larger structs, we can use `{:#?}` in place of `{:?}` in the `println!` string.
|
||||
If we use the pretty debug style in this example, the output will look like:
|
||||
for this instance, which would definitely help during debugging. When we have
|
||||
larger structs, it’s useful to have output that’s a bit easier to read; in
|
||||
those cases, we can use `{:#?}` instead of `{:?}` in the `println!` string.
|
||||
When we use the `{:#?}` style in the example, the output will look like this:
|
||||
|
||||
```text
|
||||
rect1 is Rectangle {
|
||||
@@ -379,15 +371,13 @@ rect1 is Rectangle {
|
||||
}
|
||||
```
|
||||
|
||||
There are a number of traits Rust has provided for us to use with the `derive`
|
||||
annotation that can add useful behavior to our custom types. Those traits and
|
||||
their behaviors are listed in Appendix C. We’ll be covering how to implement
|
||||
these traits with custom behavior, as well as creating your own traits, in
|
||||
Chapter 10.
|
||||
Rust has provided a number of traits for us to use with the `derive` annotation
|
||||
that can add useful behavior to our custom types. Those traits and their
|
||||
behaviors are listed in Appendix C. We’ll cover how to implement these traits
|
||||
with custom behavior as well as how to create your own traits in Chapter 10.
|
||||
|
||||
Our `area` function is pretty specific—it only computes the area of rectangles.
|
||||
It would be nice to tie this behavior together more closely with our
|
||||
`Rectangle` struct, since it’s behavior that our `Rectangle` type has
|
||||
specifically. Let’s now look at how we can continue to refactor this code by
|
||||
turning the `area` function into an `area` *method* defined on our `Rectangle`
|
||||
type.
|
||||
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 won't work with any other type. Let’s look at how we can
|
||||
continue to refactor this code by turning the `area` function into an `area`
|
||||
*method* defined on our `Rectangle` type.
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
*Methods* are similar to functions: they’re declared with the `fn` keyword and
|
||||
their name, they can have parameters and return values, and they contain some
|
||||
code that gets run when they’re called from somewhere else. Methods are
|
||||
different from functions, however, because they’re defined within the context
|
||||
of a struct (or an enum or a trait object, which we will cover in Chapters 6
|
||||
and 17, respectively), and their first parameter is always `self`, which
|
||||
represents the instance of the struct that the method is being called on.
|
||||
code that is run when they’re called from somewhere else. However, methods are
|
||||
different from functions in that they’re defined within the context of a struct
|
||||
(or an enum or a trait object, which we cover in Chapters 6 and 17,
|
||||
respectively), and their first parameter is always `self`, which represents the
|
||||
instance of the struct the method is being called on.
|
||||
|
||||
### Defining Methods
|
||||
|
||||
Let’s change our `area` function that has a `Rectangle` instance as a parameter
|
||||
Let’s change the `area` function that has a `Rectangle` instance as a parameter
|
||||
and instead make an `area` method defined on the `Rectangle` struct, as shown
|
||||
in Listing 5-7:
|
||||
|
||||
@@ -42,57 +42,53 @@ fn main() {
|
||||
<span class="caption">Listing 5-7: Defining an `area` method on the `Rectangle`
|
||||
struct</span>
|
||||
|
||||
<!-- Will add ghosting and wingdings here in libreoffice /Carol -->
|
||||
To define the function within the context of `Rectangle`, we start an `impl`
|
||||
(*implementation*) block. Then we move the `area` function within the `impl`
|
||||
curly braces and change the first (and in this case, only) parameter to be
|
||||
`self` in the signature and everywhere within the body. In `main` where we
|
||||
called the `area` function and passed `rect1` as an argument, we can instead
|
||||
use *method syntax* to call the `area` method on our `Rectangle` instance.
|
||||
The method syntax goes after an instance: we add a dot followed by the method
|
||||
name, parentheses, and any arguments.
|
||||
|
||||
In order to make the function be defined within the context of `Rectangle`, we
|
||||
start an `impl` block (`impl` is short for *implementation*). Then we move the
|
||||
function within the `impl` curly braces, and change the first (and in this
|
||||
case, only) parameter to be `self` in the signature and everywhere within the
|
||||
body. Then in `main` where we called the `area` function and passed `rect1` as
|
||||
an argument, we can instead use *method syntax* to call the `area` method on
|
||||
our `Rectangle` instance. Method syntax is taking an instance and adding a dot
|
||||
followed by the method name, parentheses, and any arguments.
|
||||
|
||||
In the signature for `area`, we get to use `&self` instead of `rectangle:
|
||||
&Rectangle` because Rust knows the type of `self` is `Rectangle` due to this
|
||||
method being inside the `impl Rectangle` context. Note we still need to have
|
||||
the `&` before `self`, just like we had `&Rectangle`. Methods can choose to
|
||||
take ownership of `self`, borrow `self` immutably as we’ve done here, or borrow
|
||||
`self` mutably, just like any other parameter.
|
||||
In the signature for `area`, we use `&self` instead of `rectangle: &Rectangle`
|
||||
because Rust knows the type of `self` is `Rectangle` due to this method being
|
||||
inside the `impl Rectangle` context. Note that we still need to use the `&`
|
||||
before `self`, just like we did in `&Rectangle`. Methods can take ownership of
|
||||
`self`, borrow `self` immutably as we’ve done here, or borrow `self` mutably,
|
||||
just like any other parameter.
|
||||
|
||||
We’ve chosen `&self` here for the same reason we used `&Rectangle` in the
|
||||
function version: we don’t want to take ownership, and we just want to be able
|
||||
to read the data in the struct, not write to it. If we wanted to be able to
|
||||
change the instance that we’ve called the method on as part of what the method
|
||||
does, we’d put `&mut self` as the first parameter instead. Having a method that
|
||||
takes ownership of the instance by having just `self` as the first parameter is
|
||||
rarer; this is usually used when the method transforms `self` into something
|
||||
else and we want to prevent the caller from using the original instance after
|
||||
the transformation.
|
||||
function version: we don’t want to take ownership, and we just want to read the
|
||||
data in the struct, not write to it. If we wanted to change the instance that
|
||||
we’ve called the method on as part of what the method does, we’d use `&mut
|
||||
self` as the first parameter. Having a method that takes ownership of the
|
||||
instance by using just `self` as the first parameter is rare; this technique is
|
||||
usually used when the method transforms `self` into something else and we want
|
||||
to prevent the caller from using the original instance after the transformation.
|
||||
|
||||
The main benefit of using methods over functions, in addition to getting to use
|
||||
The main benefit of using methods instead of functions, in addition to using
|
||||
method syntax and not having to repeat the type of `self` in every method’s
|
||||
signature, is for organization. We’ve put all the things we can do with an
|
||||
instance of a type together in one `impl` block, rather than make future users
|
||||
of our code search for capabilities of `Rectangle` all over the place.
|
||||
instance of a type in one `impl` block rather than making future users of our
|
||||
code search for capabilities of `Rectangle` in various places in the library we
|
||||
provide.
|
||||
|
||||
<!-- PROD: START BOX -->
|
||||
|
||||
> ### Where’s the `->` operator?
|
||||
> ### Where’s the `->` Operator?
|
||||
>
|
||||
> In languages like C++, there are two different operators for calling methods:
|
||||
> `.` if you’re calling a method on the object directly, and `->` if you’re
|
||||
> calling the method on a pointer to the object and thus need to dereference the
|
||||
> pointer first. In other words, if `object` is a pointer, `object->something()`
|
||||
> is like `(*object).something()`.
|
||||
> In languages like C++, two different operators are used for calling methods:
|
||||
> you use `.` if you’re calling a method on the object directly and `->` if
|
||||
> you’re calling the method on a pointer to the object and need to dereference
|
||||
> the pointer first. In other words, if `object` is a pointer,
|
||||
> `object->something()` is similar to `(*object).something()`.
|
||||
>
|
||||
> Rust doesn’t have an equivalent to the `->` operator; instead, Rust has a
|
||||
> feature called *automatic referencing and dereferencing*. Calling methods is
|
||||
> one of the few places in Rust that has behavior like this.
|
||||
> one of the few places in Rust that has this behavior.
|
||||
>
|
||||
> Here’s how it works: when you call a method with `object.something()`, Rust
|
||||
> will automatically add in `&`, `&mut`, or `*` so that `object` matches the
|
||||
> signature of the method. In other words, these are the same:
|
||||
> automatically adds in `&`, `&mut`, or `*` so `object` matches the signature of
|
||||
> the method. In other words, the following are the same:
|
||||
>
|
||||
> ```rust
|
||||
> # #[derive(Debug,Copy,Clone)]
|
||||
@@ -115,22 +111,21 @@ of our code search for capabilities of `Rectangle` all over the place.
|
||||
> (&p1).distance(&p2);
|
||||
> ```
|
||||
>
|
||||
> The first one looks much, much cleaner. This automatic referencing behavior
|
||||
> works because methods have a clear receiver — the type of `self`. Given the
|
||||
> receiver and name of a method, Rust can figure out definitively whether the
|
||||
> method is just reading (so needs `&self`), mutating (so `&mut self`), or
|
||||
> consuming (so `self`). The fact that Rust makes borrowing implicit for method
|
||||
> receivers is a big part of making ownership ergonomic in practice.
|
||||
|
||||
<!-- PROD: END BOX -->
|
||||
> The first one looks much cleaner. This automatic referencing behavior works
|
||||
> because methods have a clear receiver—the type of `self`. Given the receiver
|
||||
> and name of a method, Rust can figure out definitively whether the method is
|
||||
> reading (`&self`), mutating (`&mut self`), or consuming (`self`). The fact
|
||||
> that Rust makes borrowing implicit for method receivers is a big part of
|
||||
> making ownership ergonomic in practice.
|
||||
|
||||
### Methods with More Parameters
|
||||
|
||||
Let’s practice some more with methods by implementing a second method on our
|
||||
`Rectangle` struct. This time, we’d like for an instance of `Rectangle` to take
|
||||
another instance of `Rectangle` and return `true` if the second rectangle could
|
||||
fit completely within `self` and `false` if it would not. That is, if we run
|
||||
the code in Listing 5-8, once we've defined the `can_hold` method:
|
||||
Let’s practice using methods by implementing a second method on the `Rectangle`
|
||||
struct. This time, we want an instance of `Rectangle` to take another instance
|
||||
of `Rectangle` and return `true` if the second `R``ectangle` can fit completely
|
||||
within `self`; otherwise it should return `false`. That is, we want to be able
|
||||
to write the program shown in Listing 5-8, once we’ve defined the `can_hold`
|
||||
method:
|
||||
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
@@ -148,8 +143,9 @@ fn main() {
|
||||
<span class="caption">Listing 5-8: Demonstration of using the as-yet-unwritten
|
||||
`can_hold` method</span>
|
||||
|
||||
We want to see this output, since both of `rect2`’s dimensions are smaller than
|
||||
`rect1`’s, but `rect3` is wider than `rect1`:
|
||||
And the expected output would look like the following, because both dimensions
|
||||
of `rect2` are smaller than the dimensions of `rect1`, but `rect3` is wider
|
||||
than `rect1`:
|
||||
|
||||
```text
|
||||
Can rect1 hold rect2? true
|
||||
@@ -159,17 +155,17 @@ Can rect1 hold rect3? false
|
||||
We know we want to define a method, so it will be within the `impl Rectangle`
|
||||
block. The method name will be `can_hold`, and it will take an immutable borrow
|
||||
of another `Rectangle` as a parameter. We can tell what the type of the
|
||||
parameter will be by looking at a call site: `rect1.can_hold(&rect2)` passes in
|
||||
`&rect2`, which is an immutable borrow to `rect2`, an instance of `Rectangle`.
|
||||
This makes sense, since we only need to read `rect2` (rather than write, which
|
||||
would mean we’d need a mutable borrow) and we want `main` to keep ownership of
|
||||
`rect2` so that we could use it again after calling this method. The return
|
||||
value of `can_hold` will be a boolean, and the implementation will check to see
|
||||
if `self`’s length and width are both greater than the length and width of the
|
||||
other `Rectangle`, respectively. Let’s add this new method to the `impl` block
|
||||
from Listing 5-7, shown in Listing 5-9:
|
||||
parameter will be by looking at the code that calls the method:
|
||||
`rect1.can_hold(&rect2)` passes in `&rect2`, which is an immutable borrow to
|
||||
`rect2`, an instance of `Rectangle`. This makes sense because we only need to
|
||||
read `rect2` (rather than write, which would mean we’d need a mutable borrow),
|
||||
and we want `main` to retain ownership of `rect2` so we can use it again after
|
||||
calling the `can_hold` method. The return value of `can_hold` will be a
|
||||
boolean, and the implementation will check whether the length and width of
|
||||
`self` are both greater than the length and width of the other `Rectangle`,
|
||||
respectively. Let’s add the new `can_hold` method to the `impl` block from
|
||||
Listing 5-7, shown in Listing 5-9:
|
||||
|
||||
<figure>
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
@@ -190,28 +186,22 @@ impl Rectangle {
|
||||
}
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
<span class="caption">Listing 5-9: Implementing the `can_hold` method on
|
||||
`Rectangle` that takes another `Rectangle` instance as a parameter </span>
|
||||
|
||||
Listing 5-9: Implementing the `can_hold` method on `Rectangle` that takes
|
||||
another `Rectangle` instance as an argument
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- Will add ghosting here in libreoffice /Carol -->
|
||||
|
||||
If we run this with the `main` from Listing 5-8, we will get our desired output!
|
||||
Methods can have multiple parameters that we add to the signature after the
|
||||
`self` parameter, and those parameters work just like parameters in functions
|
||||
do.
|
||||
When we run this code with the `main` function in Listing 5-8, we’ll get our
|
||||
desired output. Methods can take multiple parameters that we add to the
|
||||
signature after the `self` parameter, and those parameters work just like
|
||||
parameters in functions.
|
||||
|
||||
### Associated Functions
|
||||
|
||||
One more useful feature of `impl` blocks: we’re allowed to define functions
|
||||
within `impl` blocks that *don’t* take `self` as a parameter. These are called
|
||||
*associated functions*, since they’re associated with the struct. They’re still
|
||||
functions though, not methods, since they don’t have an instance of the struct
|
||||
to work with. You’ve already used an associated function: `String::from`.
|
||||
Another useful feature of `impl` blocks is that we’re allowed to define
|
||||
functions within `impl` blocks that *don’t* take `self` as a parameter. These
|
||||
are called *associated functions* because they’re associated with the struct.
|
||||
They’re still functions, not methods, because they don’t have an instance of
|
||||
the struct to work with. You’ve already used the `String::from` associated
|
||||
function.
|
||||
|
||||
Associated functions are often used for constructors that will return a new
|
||||
instance of the struct. For example, we could provide an associated function
|
||||
@@ -235,10 +225,10 @@ impl Rectangle {
|
||||
}
|
||||
```
|
||||
|
||||
To call this associated function, we use the `::` syntax with the struct name:
|
||||
`let sq = Rectangle::square(3);`, for example. This function is namespaced by
|
||||
the struct: the `::` syntax is used for both associated functions and
|
||||
namespaces created by modules, which we’ll learn about in Chapter 7.
|
||||
To call this associated function, we use the `::` syntax with the struct name,
|
||||
like `let sq = Rectangle::square(3);`, for example. This function is
|
||||
namespaced by the struct: the `::` syntax is used for both associated functions
|
||||
and namespaces created by modules, which we'll discuss in Chapter 7.
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -249,5 +239,6 @@ instances of our structs have, and associated functions let us namespace
|
||||
functionality that is particular to our struct without having an instance
|
||||
available.
|
||||
|
||||
Structs aren’t the only way we can create custom types, though; let’s turn to
|
||||
the `enum` feature of Rust and add another tool to our toolbox.
|
||||
But structs aren’t the only way we can create custom types: let’s turn to
|
||||
Rust’s enum feature to add another tool to our toolbox.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user