Edits to chapter 5 after copy editing review

This commit is contained in:
Carol (Nichols || Goulding)
2017-04-25 16:49:52 -04:00
parent f9d55c0f11
commit b66bdad8e2
2 changed files with 251 additions and 270 deletions

View File

@@ -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 objects data
attributes. In the next section of this chapter, well talk about how to define
methods on our structs; methods are how you specify the *behavior* that goes
along with a structs data. The `struct` and `enum` (that we will talk about in
Chapter 6) concepts are the building blocks for creating new types in your
programs domain in order to take full advantage of Rusts 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 youre
familiar with an object-oriented language, a *struct* is like an objects 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 programs domain to take full
advantage of Rusts 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 its clearer what the
values mean. Structs are more flexible as a result of these names: we dont
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 structs 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 fields 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 its clear what the values mean. As a result of these names,
structs are more flexible than tuples: we dont 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
structs 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 dont 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 weve 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 dont 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 users 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 users 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.
>
> Its 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. Lets 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
> ```
>
> Well discuss how to fix these errors so you can store references in structs
> in Chapter 10, but for now, well 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, lets write a program that
calculates the area of a rectangle. Well start off with single variables, then
refactor our program until were using structs instead.
calculates the area of a rectangle. Well start with 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 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 projects *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>
Lets 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 thats 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 thats
not expressed anywhere in our program. It would be more readable and more
manageable to group length and width together.
Weve already discussed one way we might do that in Chapter 3: tuples. Listing
5-3 has a version of our program which uses tuples:
Weve 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
were now passing just one argument when we call `area`. But in another way
this method is less clear: tuples dont 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
were now passing just one argument. But in another way this version is less
clear: tuples dont name their elements, so our calculation has become more
confusing because we have to index into the parts of the tuple.
It doesnt 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 havent 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 havent 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
were 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 weve 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 weve 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 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.
Our `area` function is now defined with one parameter that weve 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 thats 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
Itd be nice to be able to print out an instance of our `Rectangle` while were
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 were 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 weve seen so far implement
`Display` by default, as 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 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 doesnt try to guess what we
want and structs do not have a provided implementation of `Display`.
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 braces? 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`.
If we keep reading the errors, though, well find this helpful note:
If we continue reading the errors, well find this helpful note:
```text
note: `Rectangle` cannot be formatted with the default formatter; try using
`:?` instead if you are using a format string
```
Lets 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.
Lets 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 were debugging our code.
Lets 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 wont get any errors and well see
the following output:
Now when we run the program, we wont get any errors and well see the
following output:
```text
rect1 is Rectangle { length: 50, width: 30 }
```
Nice! Its 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, its useful to have output thats 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. Well 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. Well 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 specificit only computes the area of rectangles.
It would be nice to tie this behavior together more closely with our
`Rectangle` struct, since its behavior that our `Rectangle` type has
specifically. Lets 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. 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.

View File

@@ -2,15 +2,15 @@
*Methods* are similar to functions: theyre declared with the `fn` keyword and
their name, they can have parameters and return values, and they contain some
code that gets run when theyre called from somewhere else. Methods are
different from functions, however, because theyre 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 theyre called from somewhere else. However, methods are
different from functions in that theyre 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
Lets change our `area` function that has a `Rectangle` instance as a parameter
Lets 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 weve 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 weve done here, or borrow `self` mutably,
just like any other parameter.
Weve chosen `&self` here for the same reason we used `&Rectangle` in the
function version: we dont 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 weve called the method on as part of what the method
does, wed 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 dont 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
weve called the method on as part of what the method does, wed 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 methods
signature, is for organization. Weve 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 -->
> ### Wheres the `->` operator?
> ### Wheres the `->` Operator?
>
> In languages like C++, there are two different operators for calling methods:
> `.` if youre calling a method on the object directly, and `->` if youre
> 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 youre calling a method on the object directly and `->` if
> youre 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 doesnt 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.
>
> Heres 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 receiverthe 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 receiverthe 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
Lets practice some more with methods by implementing a second method on our
`Rectangle` struct. This time, wed 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:
Lets 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 weve 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 wed 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. Lets 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 wed 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. Lets 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, well 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: were allowed to define functions
within `impl` blocks that *dont* take `self` as a parameter. These are called
*associated functions*, since theyre associated with the struct. Theyre still
functions though, not methods, since they dont have an instance of the struct
to work with. Youve already used an associated function: `String::from`.
Another useful feature of `impl` blocks is that were allowed to define
functions within `impl` blocks that *dont* take `self` as a parameter. These
are called *associated functions* because theyre associated with the struct.
Theyre still functions, not methods, because they dont have an instance of
the struct to work with. Youve 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 well 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 arent the only way we can create custom types, though; lets turn to
the `enum` feature of Rust and add another tool to our toolbox.
But structs arent the only way we can create custom types: lets turn to
Rusts enum feature to add another tool to our toolbox.