]>
Commit | Line | Data |
---|---|---|
cc61c64b XL |
1 | ## Generic Data Types |
2 | ||
0531ce1d XL |
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. | |
cc61c64b | 7 | |
0531ce1d | 8 | ### In Function Definitions |
cc61c64b | 9 | |
0531ce1d XL |
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. | |
cc61c64b | 14 | |
0531ce1d XL |
15 | Continuing with our `largest` function, Listing 10-4 shows two functions that |
16 | both find the largest value in a slice: | |
cc61c64b XL |
17 | |
18 | <span class="filename">Filename: src/main.rs</span> | |
19 | ||
20 | ```rust | |
21 | fn largest_i32(list: &[i32]) -> i32 { | |
22 | let mut largest = list[0]; | |
23 | ||
24 | for &item in list.iter() { | |
25 | if item > largest { | |
26 | largest = item; | |
27 | } | |
28 | } | |
29 | ||
30 | largest | |
31 | } | |
32 | ||
33 | fn largest_char(list: &[char]) -> char { | |
34 | let mut largest = list[0]; | |
35 | ||
36 | for &item in list.iter() { | |
37 | if item > largest { | |
38 | largest = item; | |
39 | } | |
40 | } | |
41 | ||
42 | largest | |
43 | } | |
44 | ||
45 | fn main() { | |
3b2f2976 | 46 | let number_list = vec![34, 50, 25, 100, 65]; |
cc61c64b | 47 | |
3b2f2976 | 48 | let result = largest_i32(&number_list); |
cc61c64b XL |
49 | println!("The largest number is {}", result); |
50 | # assert_eq!(result, 100); | |
51 | ||
3b2f2976 | 52 | let char_list = vec!['y', 'm', 'a', 'q']; |
cc61c64b | 53 | |
3b2f2976 | 54 | let result = largest_char(&char_list); |
cc61c64b XL |
55 | println!("The largest char is {}", result); |
56 | # assert_eq!(result, 'y'); | |
57 | } | |
58 | ``` | |
59 | ||
60 | <span class="caption">Listing 10-4: Two functions that differ only in their | |
61 | names and the types in their signatures</span> | |
62 | ||
0531ce1d XL |
63 | The `largest_i32` function is the one we extracted in Listing 10-3 that finds |
64 | the largest `i32` in a slice. The `largest_char` function finds the largest | |
65 | `char` in a slice. The function bodies have the same code, so let’s eliminate | |
66 | the duplication by introducing a generic type parameter in a single function. | |
cc61c64b | 67 | |
0531ce1d XL |
68 | To parameterize the types in the new function we’ll define, we need to name the |
69 | type parameter, just like we do for the value parameters to a function. You can | |
70 | use any identifier as a type parameter name. But we’ll use `T` because, by | |
71 | convention, parameter names in Rust are short, often just a letter, and Rust’s | |
72 | type naming convention is CamelCase. Short for “type,” `T` is the default | |
73 | choice of most Rust programmers. | |
cc61c64b XL |
74 | |
75 | When we use a parameter in the body of the function, we have to declare the | |
0531ce1d XL |
76 | parameter name in the signature so that the compiler knows what that name |
77 | means. Similarly, when we use a type parameter name in a function signature, we | |
78 | have to declare the type parameter name before we use it. To define the generic | |
79 | `largest` function, place type name declarations inside angle brackets (`<>`) | |
80 | between the name of the function and the parameter list, like this: | |
cc61c64b XL |
81 | |
82 | ```rust,ignore | |
83 | fn largest<T>(list: &[T]) -> T { | |
84 | ``` | |
85 | ||
0531ce1d XL |
86 | We read this definition as: the function `largest` is generic over some type |
87 | `T`. This function has one parameter named `list`, which is a slice of values | |
88 | of type `T`. The `largest` function will return a value of the same type `T`. | |
cc61c64b | 89 | |
0531ce1d XL |
90 | Listing 10-5 shows the combined `largest` function definition using the generic |
91 | data type in its signature. The listing also shows how we can call the function | |
92 | with either a slice of `i32` values or `char` values. Note that this code won’t | |
93 | compile yet, but we’ll fix it later in this chapter. | |
cc61c64b XL |
94 | |
95 | <span class="filename">Filename: src/main.rs</span> | |
96 | ||
97 | ```rust,ignore | |
98 | fn largest<T>(list: &[T]) -> T { | |
99 | let mut largest = list[0]; | |
100 | ||
101 | for &item in list.iter() { | |
102 | if item > largest { | |
103 | largest = item; | |
104 | } | |
105 | } | |
106 | ||
107 | largest | |
108 | } | |
109 | ||
110 | fn main() { | |
3b2f2976 | 111 | let number_list = vec![34, 50, 25, 100, 65]; |
cc61c64b | 112 | |
3b2f2976 | 113 | let result = largest(&number_list); |
cc61c64b XL |
114 | println!("The largest number is {}", result); |
115 | ||
3b2f2976 | 116 | let char_list = vec!['y', 'm', 'a', 'q']; |
cc61c64b | 117 | |
3b2f2976 | 118 | let result = largest(&char_list); |
cc61c64b XL |
119 | println!("The largest char is {}", result); |
120 | } | |
121 | ``` | |
122 | ||
123 | <span class="caption">Listing 10-5: A definition of the `largest` function that | |
3b2f2976 | 124 | uses generic type parameters but doesn’t compile yet</span> |
cc61c64b | 125 | |
0531ce1d | 126 | If we compile this code right now, we’ll get this error: |
cc61c64b XL |
127 | |
128 | ```text | |
129 | error[E0369]: binary operation `>` cannot be applied to type `T` | |
0531ce1d | 130 | --> src/main.rs:5:12 |
cc61c64b XL |
131 | | |
132 | 5 | if item > largest { | |
0531ce1d | 133 | | ^^^^^^^^^^^^^^ |
cc61c64b | 134 | | |
0531ce1d | 135 | = note: an implementation of `std::cmp::PartialOrd` might be missing for `T` |
cc61c64b XL |
136 | ``` |
137 | ||
0531ce1d XL |
138 | The note mentions `std::cmp::PartialOrd`, which is a *trait*. We’ll talk about |
139 | traits in the next section. For now, this error states that the body of | |
140 | `largest` won’t work for all possible types that `T` could be. Because we want | |
141 | to compare values of type `T` in the body, we can only use types whose values | |
142 | can be ordered. To enable comparisons, the standard library has the | |
143 | `std::cmp::PartialOrd` trait that you can implement on types (see Appendix C, | |
144 | “Derivable Traits,” for more on this trait). You’ll learn how to specify that a | |
145 | generic type has a particular trait in the “Trait Bounds” section, but let’s | |
146 | first explore other ways of using generic type parameters. | |
147 | ||
148 | ### In Struct Definitions | |
149 | ||
150 | We can also define structs to use a generic type parameter in one or more | |
151 | fields using the `<>` syntax. Listing 10-6 shows how to define a `Point<T>` | |
152 | struct to hold `x` and `y` coordinate values of any type: | |
cc61c64b XL |
153 | |
154 | <span class="filename">Filename: src/main.rs</span> | |
155 | ||
156 | ```rust | |
157 | struct Point<T> { | |
158 | x: T, | |
159 | y: T, | |
160 | } | |
161 | ||
162 | fn main() { | |
163 | let integer = Point { x: 5, y: 10 }; | |
164 | let float = Point { x: 1.0, y: 4.0 }; | |
165 | } | |
166 | ``` | |
167 | ||
0531ce1d | 168 | <span class="caption">Listing 10-6: A `Point<T>` struct that holds `x` and `y` |
cc61c64b XL |
169 | values of type `T`</span> |
170 | ||
0531ce1d XL |
171 | The syntax for using generics in struct definitions is similar to that used in |
172 | function definitions. First, we declare the name of the type parameter inside | |
173 | angle brackets just after the name of the struct. Then we can use the generic | |
174 | type in the struct definition where we would otherwise specify concrete data | |
175 | types. | |
cc61c64b | 176 | |
0531ce1d XL |
177 | Note that because we’ve only used one generic type to define `Point<T>`, this |
178 | says that the `Point<T>` struct is generic over some type `T`, and the fields | |
179 | `x` and `y` are *both* that same type, whatever that type may be. This means | |
180 | that if we create an instance of a `Point<T>` that has values of different | |
181 | types, as in Listing 10-7, our code won’t compile: | |
cc61c64b XL |
182 | |
183 | <span class="filename">Filename: src/main.rs</span> | |
184 | ||
185 | ```rust,ignore | |
186 | struct Point<T> { | |
187 | x: T, | |
188 | y: T, | |
189 | } | |
190 | ||
191 | fn main() { | |
192 | let wont_work = Point { x: 5, y: 4.0 }; | |
193 | } | |
194 | ``` | |
195 | ||
196 | <span class="caption">Listing 10-7: The fields `x` and `y` must be the same | |
197 | type because both have the same generic data type `T`</span> | |
198 | ||
0531ce1d XL |
199 | In this example, when we assign the integer value `5` to `x`, we let the |
200 | compiler know that the generic type `T` will be an integer for this instance of | |
201 | `Point<T>`. Then when we specify `4.0` for `y`, which we’ve defined to have the | |
202 | same type as `x`, we’ll get a type mismatch error like this: | |
cc61c64b XL |
203 | |
204 | ```text | |
205 | error[E0308]: mismatched types | |
0531ce1d | 206 | --> src/main.rs:7:38 |
cc61c64b XL |
207 | | |
208 | 7 | let wont_work = Point { x: 5, y: 4.0 }; | |
209 | | ^^^ expected integral variable, found | |
0531ce1d | 210 | floating-point variable |
cc61c64b XL |
211 | | |
212 | = note: expected type `{integer}` | |
0531ce1d | 213 | found type `{float}` |
cc61c64b XL |
214 | ``` |
215 | ||
0531ce1d XL |
216 | To define a `Point` struct where `x` and `y` are both generics but could have |
217 | different types, we can use multiple generic type parameters. For example, in | |
218 | Listing 10-8, we can change the definition of `Point` to be generic over types | |
219 | `T` and `U` where `x` is of type `T` and `y` is of type `U`: | |
cc61c64b XL |
220 | |
221 | <span class="filename">Filename: src/main.rs</span> | |
222 | ||
223 | ```rust | |
224 | struct Point<T, U> { | |
225 | x: T, | |
226 | y: U, | |
227 | } | |
228 | ||
229 | fn main() { | |
230 | let both_integer = Point { x: 5, y: 10 }; | |
231 | let both_float = Point { x: 1.0, y: 4.0 }; | |
232 | let integer_and_float = Point { x: 5, y: 4.0 }; | |
233 | } | |
234 | ``` | |
235 | ||
0531ce1d XL |
236 | <span class="caption">Listing 10-8: A `Point<T, U>` generic over two types so |
237 | that `x` and `y` can be values of different types</span> | |
cc61c64b | 238 | |
0531ce1d XL |
239 | Now all the instances of `Point` shown are allowed! You can use as many generic |
240 | type parameters in a definition as you want, but using more than a few makes | |
241 | your code hard to read. When you need lots of generic types in your code, it | |
242 | could indicate that your code needs restructuring into smaller pieces. | |
cc61c64b | 243 | |
0531ce1d | 244 | ### In Enum Definitions |
cc61c64b | 245 | |
0531ce1d XL |
246 | As we did with structs, we can define enums to hold generic data types in their |
247 | variants. Let’s take another look at the `Option<T>` enum that the standard | |
248 | library provides that we used in Chapter 6: | |
cc61c64b XL |
249 | |
250 | ```rust | |
251 | enum Option<T> { | |
252 | Some(T), | |
253 | None, | |
254 | } | |
255 | ``` | |
256 | ||
0531ce1d XL |
257 | This definition should now make more sense to you. As you can see, `Option<T>` |
258 | is an enum that is generic over type `T` and has two variants: `Some`, which | |
259 | holds one value of type `T`, and a `None` variant that doesn’t hold any value. | |
260 | By using the `Option<T>` enum, we can express the abstract concept of having an | |
261 | optional value, and because `Option<T>` is generic, we can use this abstraction | |
262 | no matter what the type of the optional value is. | |
cc61c64b XL |
263 | |
264 | Enums can use multiple generic types as well. The definition of the `Result` | |
265 | enum that we used in Chapter 9 is one example: | |
266 | ||
267 | ```rust | |
268 | enum Result<T, E> { | |
269 | Ok(T), | |
270 | Err(E), | |
271 | } | |
272 | ``` | |
273 | ||
0531ce1d XL |
274 | The `Result` enum is generic over two types, `T` and `E`, and has two variants: |
275 | `Ok`, which holds a value of type `T`, and `Err`, which holds a value of type | |
276 | `E`. This definition makes it convenient to use the `Result` enum anywhere we | |
277 | have an operation that might succeed (return a value of some type `T`) or fail | |
278 | (return an error of some type `E`). In fact, this is what we used to open a | |
279 | file in Listing 9-3 where `T` was filled in with the type `std::fs::File` when | |
280 | the file was opened successfully and `E` was filled in with the type | |
281 | `std::io::Error` when there were problems opening the file. | |
cc61c64b XL |
282 | |
283 | When you recognize situations in your code with multiple struct or enum | |
284 | definitions that differ only in the types of the values they hold, you can | |
0531ce1d | 285 | avoid duplication by using generic types instead. |
cc61c64b | 286 | |
0531ce1d | 287 | ### In Method Definitions |
cc61c64b | 288 | |
0531ce1d XL |
289 | As we did in Chapter 5, we can implement methods on structs and enums that have |
290 | generic types in their definitions. Listing 10-9 shows the `Point<T>` struct we | |
291 | defined in Listing 10-6 with a method named `x` implemented on it: | |
cc61c64b XL |
292 | |
293 | <span class="filename">Filename: src/main.rs</span> | |
294 | ||
295 | ```rust | |
296 | struct Point<T> { | |
297 | x: T, | |
298 | y: T, | |
299 | } | |
300 | ||
301 | impl<T> Point<T> { | |
302 | fn x(&self) -> &T { | |
303 | &self.x | |
304 | } | |
305 | } | |
306 | ||
307 | fn main() { | |
308 | let p = Point { x: 5, y: 10 }; | |
309 | ||
310 | println!("p.x = {}", p.x()); | |
311 | } | |
312 | ``` | |
313 | ||
314 | <span class="caption">Listing 10-9: Implementing a method named `x` on the | |
0531ce1d XL |
315 | `Point<T>` struct that will return a reference to the `x` field of type |
316 | `T`</span> | |
cc61c64b | 317 | |
0531ce1d XL |
318 | Here, we’ve defined a method named `x` on `Point<T>` that returns a reference |
319 | to the data in the field `x`. | |
320 | ||
321 | Note that we have to declare `T` just after `impl` so we can use it to specify | |
322 | that we’re implementing methods on the type `Point<T>`. By declaring `T` as a | |
323 | generic type after `impl`, Rust can identify that the type in the angle | |
324 | brackets in `Point` is a generic type rather than a concrete type. | |
325 | ||
326 | We could, for example, implement methods only on `Point<f32>` instances rather | |
327 | than on `Point<T>` instances with any generic type. In Listing 10-10 we use the | |
328 | concrete type `f32`, meaning we don’t declare any types after `impl`: | |
cc61c64b | 329 | |
3b2f2976 XL |
330 | ```rust |
331 | # struct Point<T> { | |
332 | # x: T, | |
333 | # y: T, | |
334 | # } | |
335 | # | |
336 | impl Point<f32> { | |
337 | fn distance_from_origin(&self) -> f32 { | |
338 | (self.x.powi(2) + self.y.powi(2)).sqrt() | |
339 | } | |
340 | } | |
341 | ``` | |
342 | ||
0531ce1d XL |
343 | <span class="caption">Listing 10-10: An `impl` block that only applies to a |
344 | struct with a particular concrete type for the generic type parameter `T`</span> | |
3b2f2976 XL |
345 | |
346 | This code means the type `Point<f32>` will have a method named | |
347 | `distance_from_origin`, and other instances of `Point<T>` where `T` is not of | |
0531ce1d XL |
348 | type `f32` will not have this method defined. The method measures how far our |
349 | point is from the point at coordinates (0.0, 0.0) and uses mathematical | |
350 | operations that are only available for floating point types. | |
351 | ||
352 | Generic type parameters in a struct definition aren’t always the same as those | |
353 | you use in that struct’s method signatures. For example, Listing 10-11 defines | |
354 | the method `mixup` on the `Point<T, U>` struct from Listing 10-8. The method | |
355 | takes another `Point` as a parameter, which might have different types than the | |
356 | `self` `Point` we’re calling `mixup` on. The method creates a new `Point` | |
357 | instance with the `x` value from the `self` `Point` (of type `T`) and the `y` | |
358 | value from the passed-in `Point` (of type `W`): | |
cc61c64b XL |
359 | |
360 | <span class="filename">Filename: src/main.rs</span> | |
361 | ||
362 | ```rust | |
363 | struct Point<T, U> { | |
364 | x: T, | |
365 | y: U, | |
366 | } | |
367 | ||
368 | impl<T, U> Point<T, U> { | |
369 | fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> { | |
370 | Point { | |
371 | x: self.x, | |
372 | y: other.y, | |
373 | } | |
374 | } | |
375 | } | |
376 | ||
377 | fn main() { | |
378 | let p1 = Point { x: 5, y: 10.4 }; | |
379 | let p2 = Point { x: "Hello", y: 'c'}; | |
380 | ||
381 | let p3 = p1.mixup(p2); | |
382 | ||
383 | println!("p3.x = {}, p3.y = {}", p3.x, p3.y); | |
384 | } | |
385 | ``` | |
386 | ||
3b2f2976 XL |
387 | <span class="caption">Listing 10-11: Methods that use different generic types |
388 | than their struct’s definition</span> | |
cc61c64b | 389 | |
3b2f2976 | 390 | In `main`, we’ve defined a `Point` that has an `i32` for `x` (with value `5`) |
0531ce1d XL |
391 | and an `f64` for `y` (with value `10.4`). The `p2` variable is a `Point` struct |
392 | that has a string slice for `x` (with value `"Hello"`) and a `char` for `y` | |
393 | (with value `c`). Calling `mixup` on `p1` with the argument `p2` gives us `p3`, | |
394 | which will have an `i32` for `x`, because `x` came from `p1`. The `p3` variable | |
395 | will have a `char` for `y`, because `y` came from `p2`. The `println!` macro | |
396 | call will print `p3.x = 5, p3.y = c`. | |
397 | ||
398 | The purpose of this example is to demonstrate a situation in which some generic | |
399 | parameters are declared with `impl` and some are declared with the method | |
400 | definition. Here, the generic parameters `T` and `U` are declared after `impl`, | |
401 | because they go with the struct definition. The generic parameters `V` and `W` | |
402 | are declared after `fn mixup`, because they’re only relevant to the method. | |
cc61c64b XL |
403 | |
404 | ### Performance of Code Using Generics | |
405 | ||
0531ce1d XL |
406 | You might be wondering whether there is a runtime cost when you’re using |
407 | generic type parameters. The good news is that Rust implements generics in such | |
408 | a way that your code doesn’t run any slower using generic types than it would | |
409 | with concrete types. | |
cc61c64b | 410 | |
0531ce1d XL |
411 | Rust accomplishes this by performing monomorphization of the code that is using |
412 | generics at compile time. *Monomorphization* is the process of turning generic | |
413 | code into specific code by filling in the concrete types that are used when | |
414 | compiled. | |
cc61c64b | 415 | |
0531ce1d XL |
416 | In this process, the compiler does the opposite of the steps we used to create |
417 | the generic function in Listing 10-5: the compiler looks at all the places | |
418 | where generic code is called and generates code for the concrete types the | |
cc61c64b XL |
419 | generic code is called with. |
420 | ||
0531ce1d XL |
421 | Let’s look at how this works with an example that uses the standard library’s |
422 | `Option<T>` enum: | |
cc61c64b XL |
423 | |
424 | ```rust | |
425 | let integer = Some(5); | |
426 | let float = Some(5.0); | |
427 | ``` | |
428 | ||
0531ce1d XL |
429 | When Rust compiles this code, it performs monomorphization. During that |
430 | process, the compiler reads the values that have been used in the instances of | |
431 | `Option<T>` and identifies two kinds of `Option<T>`: one is `i32` and the other | |
432 | is `f64`. As such, it expands the generic definition of `Option<T>` into | |
433 | `Option_i32` and `Option_f64`, thereby replacing the generic definition with | |
434 | the specific ones. | |
cc61c64b | 435 | |
0531ce1d XL |
436 | The monomorphized version of the code looks like the following. The generic |
437 | `Option<T>` is replaced with the specific definitions created by the compiler: | |
cc61c64b XL |
438 | |
439 | <span class="filename">Filename: src/main.rs</span> | |
440 | ||
441 | ```rust | |
442 | enum Option_i32 { | |
443 | Some(i32), | |
444 | None, | |
445 | } | |
446 | ||
447 | enum Option_f64 { | |
448 | Some(f64), | |
449 | None, | |
450 | } | |
451 | ||
452 | fn main() { | |
453 | let integer = Option_i32::Some(5); | |
454 | let float = Option_f64::Some(5.0); | |
455 | } | |
456 | ``` | |
457 | ||
0531ce1d XL |
458 | Because Rust compiles generic code into code that specifies the type in each |
459 | instance, we pay no runtime cost for using generics. When the code runs, it | |
460 | performs just like it would if we had duplicated each definition by hand. The | |
461 | process of monomorphization makes Rust’s generics extremely efficient at | |
462 | runtime. |