]>
Commit | Line | Data |
---|---|---|
13cf67c4 XL |
1 | ## Generic Data Types |
2 | ||
3 | We can use generics to create definitions for items like function signatures or | |
4 | structs, which we can then use with many different concrete data types. Let’s | |
5 | first look at how to define functions, structs, enums, and methods using | |
6 | generics. Then we’ll discuss how generics affect code performance. | |
7 | ||
8 | ### In Function Definitions | |
9 | ||
10 | When defining a function that uses generics, we place the generics in the | |
11 | signature of the function where we would usually specify the data types of the | |
12 | parameters and return value. Doing so makes our code more flexible and provides | |
13 | more functionality to callers of our function while preventing code duplication. | |
14 | ||
15 | Continuing with our `largest` function, Listing 10-4 shows two functions that | |
16 | both find the largest value in a slice. | |
17 | ||
18 | <span class="filename">Filename: src/main.rs</span> | |
19 | ||
20 | ```rust | |
74b04a01 | 21 | {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-04/src/main.rs:here}} |
13cf67c4 XL |
22 | ``` |
23 | ||
24 | <span class="caption">Listing 10-4: Two functions that differ only in their | |
25 | names and the types in their signatures</span> | |
26 | ||
27 | The `largest_i32` function is the one we extracted in Listing 10-3 that finds | |
28 | the largest `i32` in a slice. The `largest_char` function finds the largest | |
29 | `char` in a slice. The function bodies have the same code, so let’s eliminate | |
30 | the duplication by introducing a generic type parameter in a single function. | |
31 | ||
32 | To parameterize the types in the new function we’ll define, we need to name the | |
33 | type parameter, just as we do for the value parameters to a function. You can | |
34 | use any identifier as a type parameter name. But we’ll use `T` because, by | |
35 | convention, parameter names in Rust are short, often just a letter, and Rust’s | |
36 | type-naming convention is CamelCase. Short for “type,” `T` is the default | |
37 | choice of most Rust programmers. | |
38 | ||
39 | When we use a parameter in the body of the function, we have to declare the | |
40 | parameter name in the signature so the compiler knows what that name means. | |
41 | Similarly, when we use a type parameter name in a function signature, we have | |
42 | to declare the type parameter name before we use it. To define the generic | |
43 | `largest` function, place type name declarations inside angle brackets, `<>`, | |
44 | between the name of the function and the parameter list, like this: | |
45 | ||
46 | ```rust,ignore | |
47 | fn largest<T>(list: &[T]) -> T { | |
48 | ``` | |
49 | ||
50 | We read this definition as: the function `largest` is generic over some type | |
51 | `T`. This function has one parameter named `list`, which is a slice of values | |
52 | of type `T`. The `largest` function will return a value of the same type `T`. | |
53 | ||
54 | Listing 10-5 shows the combined `largest` function definition using the generic | |
55 | data type in its signature. The listing also shows how we can call the function | |
56 | with either a slice of `i32` values or `char` values. Note that this code won’t | |
57 | compile yet, but we’ll fix it later in this chapter. | |
58 | ||
59 | <span class="filename">Filename: src/main.rs</span> | |
60 | ||
61 | ```rust,ignore,does_not_compile | |
74b04a01 | 62 | {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-05/src/main.rs}} |
13cf67c4 XL |
63 | ``` |
64 | ||
65 | <span class="caption">Listing 10-5: A definition of the `largest` function that | |
66 | uses generic type parameters but doesn’t compile yet</span> | |
67 | ||
68 | If we compile this code right now, we’ll get this error: | |
69 | ||
70 | ```text | |
74b04a01 | 71 | {{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-05/output.txt}} |
13cf67c4 XL |
72 | ``` |
73 | ||
74 | The note mentions `std::cmp::PartialOrd`, which is a *trait*. We’ll talk about | |
75 | traits in the next section. For now, this error states that the body of | |
76 | `largest` won’t work for all possible types that `T` could be. Because we want | |
77 | to compare values of type `T` in the body, we can only use types whose values | |
78 | can be ordered. To enable comparisons, the standard library has the | |
79 | `std::cmp::PartialOrd` trait that you can implement on types (see Appendix C | |
80 | for more on this trait). You’ll learn how to specify that a generic type has a | |
9fa01778 XL |
81 | particular trait in the [“Traits as Parameters”][traits-as-parameters]<!-- |
82 | ignore --> section, but let’s first explore other ways of using generic type | |
83 | parameters. | |
13cf67c4 XL |
84 | |
85 | ### In Struct Definitions | |
86 | ||
87 | We can also define structs to use a generic type parameter in one or more | |
88 | fields using the `<>` syntax. Listing 10-6 shows how to define a `Point<T>` | |
89 | struct to hold `x` and `y` coordinate values of any type. | |
90 | ||
91 | <span class="filename">Filename: src/main.rs</span> | |
92 | ||
93 | ```rust | |
74b04a01 | 94 | {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-06/src/main.rs}} |
13cf67c4 XL |
95 | ``` |
96 | ||
97 | <span class="caption">Listing 10-6: A `Point<T>` struct that holds `x` and `y` | |
98 | values of type `T`</span> | |
99 | ||
100 | The syntax for using generics in struct definitions is similar to that used in | |
101 | function definitions. First, we declare the name of the type parameter inside | |
102 | angle brackets just after the name of the struct. Then we can use the generic | |
103 | type in the struct definition where we would otherwise specify concrete data | |
104 | types. | |
105 | ||
106 | Note that because we’ve used only one generic type to define `Point<T>`, this | |
107 | definition says that the `Point<T>` struct is generic over some type `T`, and | |
108 | the fields `x` and `y` are *both* that same type, whatever that type may be. If | |
109 | we create an instance of a `Point<T>` that has values of different types, as in | |
110 | Listing 10-7, our code won’t compile. | |
111 | ||
112 | <span class="filename">Filename: src/main.rs</span> | |
113 | ||
114 | ```rust,ignore,does_not_compile | |
74b04a01 | 115 | {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-07/src/main.rs}} |
13cf67c4 XL |
116 | ``` |
117 | ||
118 | <span class="caption">Listing 10-7: The fields `x` and `y` must be the same | |
119 | type because both have the same generic data type `T`.</span> | |
120 | ||
121 | In this example, when we assign the integer value 5 to `x`, we let the | |
122 | compiler know that the generic type `T` will be an integer for this instance of | |
123 | `Point<T>`. Then when we specify 4.0 for `y`, which we’ve defined to have the | |
124 | same type as `x`, we’ll get a type mismatch error like this: | |
125 | ||
126 | ```text | |
74b04a01 | 127 | {{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-07/output.txt}} |
13cf67c4 XL |
128 | ``` |
129 | ||
130 | To define a `Point` struct where `x` and `y` are both generics but could have | |
131 | different types, we can use multiple generic type parameters. For example, in | |
132 | Listing 10-8, we can change the definition of `Point` to be generic over types | |
133 | `T` and `U` where `x` is of type `T` and `y` is of type `U`. | |
134 | ||
135 | <span class="filename">Filename: src/main.rs</span> | |
136 | ||
137 | ```rust | |
74b04a01 | 138 | {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-08/src/main.rs}} |
13cf67c4 XL |
139 | ``` |
140 | ||
141 | <span class="caption">Listing 10-8: A `Point<T, U>` generic over two types so | |
142 | that `x` and `y` can be values of different types</span> | |
143 | ||
144 | Now all the instances of `Point` shown are allowed! You can use as many generic | |
145 | type parameters in a definition as you want, but using more than a few makes | |
146 | your code hard to read. When you need lots of generic types in your code, it | |
147 | could indicate that your code needs restructuring into smaller pieces. | |
148 | ||
149 | ### In Enum Definitions | |
150 | ||
151 | As we did with structs, we can define enums to hold generic data types in their | |
152 | variants. Let’s take another look at the `Option<T>` enum that the standard | |
153 | library provides, which we used in Chapter 6: | |
154 | ||
155 | ```rust | |
156 | enum Option<T> { | |
157 | Some(T), | |
158 | None, | |
159 | } | |
160 | ``` | |
161 | ||
162 | This definition should now make more sense to you. As you can see, `Option<T>` | |
163 | is an enum that is generic over type `T` and has two variants: `Some`, which | |
164 | holds one value of type `T`, and a `None` variant that doesn’t hold any value. | |
165 | By using the `Option<T>` enum, we can express the abstract concept of having an | |
166 | optional value, and because `Option<T>` is generic, we can use this abstraction | |
167 | no matter what the type of the optional value is. | |
168 | ||
169 | Enums can use multiple generic types as well. The definition of the `Result` | |
170 | enum that we used in Chapter 9 is one example: | |
171 | ||
172 | ```rust | |
173 | enum Result<T, E> { | |
174 | Ok(T), | |
175 | Err(E), | |
176 | } | |
177 | ``` | |
178 | ||
179 | The `Result` enum is generic over two types, `T` and `E`, and has two variants: | |
180 | `Ok`, which holds a value of type `T`, and `Err`, which holds a value of type | |
181 | `E`. This definition makes it convenient to use the `Result` enum anywhere we | |
182 | have an operation that might succeed (return a value of some type `T`) or fail | |
183 | (return an error of some type `E`). In fact, this is what we used to open a | |
184 | file in Listing 9-3, where `T` was filled in with the type `std::fs::File` when | |
185 | the file was opened successfully and `E` was filled in with the type | |
186 | `std::io::Error` when there were problems opening the file. | |
187 | ||
188 | When you recognize situations in your code with multiple struct or enum | |
189 | definitions that differ only in the types of the values they hold, you can | |
190 | avoid duplication by using generic types instead. | |
191 | ||
192 | ### In Method Definitions | |
193 | ||
194 | We can implement methods on structs and enums (as we did in Chapter 5) and use | |
195 | generic types in their definitions, too. Listing 10-9 shows the `Point<T>` | |
196 | struct we defined in Listing 10-6 with a method named `x` implemented on it. | |
197 | ||
198 | <span class="filename">Filename: src/main.rs</span> | |
199 | ||
200 | ```rust | |
74b04a01 | 201 | {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-09/src/main.rs}} |
13cf67c4 XL |
202 | ``` |
203 | ||
204 | <span class="caption">Listing 10-9: Implementing a method named `x` on the | |
205 | `Point<T>` struct that will return a reference to the `x` field of type | |
206 | `T`</span> | |
207 | ||
208 | Here, we’ve defined a method named `x` on `Point<T>` that returns a reference | |
209 | to the data in the field `x`. | |
210 | ||
211 | Note that we have to declare `T` just after `impl` so we can use it to specify | |
212 | that we’re implementing methods on the type `Point<T>`. By declaring `T` as a | |
213 | generic type after `impl`, Rust can identify that the type in the angle | |
214 | brackets in `Point` is a generic type rather than a concrete type. | |
215 | ||
216 | We could, for example, implement methods only on `Point<f32>` instances rather | |
217 | than on `Point<T>` instances with any generic type. In Listing 10-10 we use the | |
218 | concrete type `f32`, meaning we don’t declare any types after `impl`. | |
219 | ||
74b04a01 XL |
220 | <span class="filename">Filename: src/main.rs</span> |
221 | ||
13cf67c4 | 222 | ```rust |
74b04a01 | 223 | {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-10/src/main.rs:here}} |
13cf67c4 XL |
224 | ``` |
225 | ||
226 | <span class="caption">Listing 10-10: An `impl` block that only applies to a | |
227 | struct with a particular concrete type for the generic type parameter `T`</span> | |
228 | ||
229 | This code means the type `Point<f32>` will have a method named | |
230 | `distance_from_origin` and other instances of `Point<T>` where `T` is not of | |
231 | type `f32` will not have this method defined. The method measures how far our | |
232 | point is from the point at coordinates (0.0, 0.0) and uses mathematical | |
233 | operations that are available only for floating point types. | |
234 | ||
235 | Generic type parameters in a struct definition aren’t always the same as those | |
236 | you use in that struct’s method signatures. For example, Listing 10-11 defines | |
237 | the method `mixup` on the `Point<T, U>` struct from Listing 10-8. The method | |
532ac7d7 | 238 | takes another `Point` as a parameter, which might have different types from the |
13cf67c4 XL |
239 | `self` `Point` we’re calling `mixup` on. The method creates a new `Point` |
240 | instance with the `x` value from the `self` `Point` (of type `T`) and the `y` | |
241 | value from the passed-in `Point` (of type `W`). | |
242 | ||
243 | <span class="filename">Filename: src/main.rs</span> | |
244 | ||
245 | ```rust | |
74b04a01 | 246 | {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-11/src/main.rs}} |
13cf67c4 XL |
247 | ``` |
248 | ||
249 | <span class="caption">Listing 10-11: A method that uses different generic types | |
532ac7d7 | 250 | from its struct’s definition</span> |
13cf67c4 XL |
251 | |
252 | In `main`, we’ve defined a `Point` that has an `i32` for `x` (with value `5`) | |
253 | and an `f64` for `y` (with value `10.4`). The `p2` variable is a `Point` struct | |
254 | that has a string slice for `x` (with value `"Hello"`) and a `char` for `y` | |
255 | (with value `c`). Calling `mixup` on `p1` with the argument `p2` gives us `p3`, | |
256 | which will have an `i32` for `x`, because `x` came from `p1`. The `p3` variable | |
257 | will have a `char` for `y`, because `y` came from `p2`. The `println!` macro | |
258 | call will print `p3.x = 5, p3.y = c`. | |
259 | ||
260 | The purpose of this example is to demonstrate a situation in which some generic | |
261 | parameters are declared with `impl` and some are declared with the method | |
262 | definition. Here, the generic parameters `T` and `U` are declared after `impl`, | |
263 | because they go with the struct definition. The generic parameters `V` and `W` | |
264 | are declared after `fn mixup`, because they’re only relevant to the method. | |
265 | ||
266 | ### Performance of Code Using Generics | |
267 | ||
268 | You might be wondering whether there is a runtime cost when you’re using | |
269 | generic type parameters. The good news is that Rust implements generics in such | |
270 | a way that your code doesn’t run any slower using generic types than it would | |
271 | with concrete types. | |
272 | ||
273 | Rust accomplishes this by performing monomorphization of the code that is using | |
274 | generics at compile time. *Monomorphization* is the process of turning generic | |
275 | code into specific code by filling in the concrete types that are used when | |
276 | compiled. | |
277 | ||
278 | In this process, the compiler does the opposite of the steps we used to create | |
279 | the generic function in Listing 10-5: the compiler looks at all the places | |
280 | where generic code is called and generates code for the concrete types the | |
281 | generic code is called with. | |
282 | ||
283 | Let’s look at how this works with an example that uses the standard library’s | |
284 | `Option<T>` enum: | |
285 | ||
286 | ```rust | |
287 | let integer = Some(5); | |
288 | let float = Some(5.0); | |
289 | ``` | |
290 | ||
291 | When Rust compiles this code, it performs monomorphization. During that | |
292 | process, the compiler reads the values that have been used in `Option<T>` | |
293 | instances and identifies two kinds of `Option<T>`: one is `i32` and the other | |
294 | is `f64`. As such, it expands the generic definition of `Option<T>` into | |
295 | `Option_i32` and `Option_f64`, thereby replacing the generic definition with | |
296 | the specific ones. | |
297 | ||
298 | The monomorphized version of the code looks like the following. The generic | |
299 | `Option<T>` is replaced with the specific definitions created by the compiler: | |
300 | ||
301 | <span class="filename">Filename: src/main.rs</span> | |
302 | ||
303 | ```rust | |
304 | enum Option_i32 { | |
305 | Some(i32), | |
306 | None, | |
307 | } | |
308 | ||
309 | enum Option_f64 { | |
310 | Some(f64), | |
311 | None, | |
312 | } | |
313 | ||
314 | fn main() { | |
315 | let integer = Option_i32::Some(5); | |
316 | let float = Option_f64::Some(5.0); | |
317 | } | |
318 | ``` | |
319 | ||
320 | Because Rust compiles generic code into code that specifies the type in each | |
321 | instance, we pay no runtime cost for using generics. When the code runs, it | |
322 | performs just as it would if we had duplicated each definition by hand. The | |
323 | process of monomorphization makes Rust’s generics extremely efficient at | |
324 | runtime. | |
9fa01778 XL |
325 | |
326 | [traits-as-parameters]: ch10-02-traits.html#traits-as-parameters |