1
00:00:00,040 --> 00:00:01,520
Welcome to module 5,
2
00:00:01,630 --> 00:00:09,450
a short introduction to generic type parameters. In this module, we'll first talk about why we're making this detour.
3
00:00:10,010 --> 00:00:15,050
Then, we'll talk about what generic type parameters are and how to use them in definitions.
4
00:00:16,140 --> 00:00:16,299
We'll
5
00:00:16,300 --> 00:00:19,779
look at what calling generic code looks like and discuss the analysis
6
00:00:19,780 --> 00:00:20,850
the compiler does.
7
00:00:22,390 --> 00:00:27,050
Let's get started by clarifying the purpose of this detour in the middle of the unit on lifetimes.
8
00:00:28,580 --> 00:00:33,300
You may be familiar with the concept of code using generic types from other programming languages.
9
00:00:34,340 --> 00:00:35,720
Even if you aren't familiar,
10
00:00:35,730 --> 00:00:40,650
the ideas behind generic type parameters are an extension of the idea of types in programming at all.
11
00:00:41,740 --> 00:00:45,150
Understanding generic type parameters is a small mental leap to take.
12
00:00:45,670 --> 00:00:46,650
In contrast,
13
00:00:46,740 --> 00:00:50,420
very few programming languages have a first-class concept of lifetimes.
14
00:00:51,980 --> 00:00:54,850
Understanding lifetime parameters is a larger mental leap.
15
00:00:55,340 --> 00:00:56,120
However,
16
00:00:56,130 --> 00:01:00,770
generic type parameters and generic lifetime parameters in Rust have some similarities,
17
00:01:00,950 --> 00:01:02,630
and we hope that by relating those ideas,
18
00:01:02,640 --> 00:01:04,050
the mental leaps will be smaller.
19
00:01:05,640 --> 00:01:06,590
With that in mind,
20
00:01:06,630 --> 00:01:09,850
let's talk about using generic type parameters in definitions.
21
00:01:11,440 --> 00:01:15,940
Generic type parameters are an abstraction that allow us to write code once that can be used
22
00:01:15,950 --> 00:01:17,950
many times with different types.
23
00:01:18,990 --> 00:01:19,880
For instance,
24
00:01:19,960 --> 00:01:24,710
consider an application that has an approval workflow of many different types of domain objects:
25
00:01:24,720 --> 00:01:26,030
articles have to be approved,
26
00:01:26,240 --> 00:01:29,220
new users have to be approved, and comments have to be approved.
27
00:01:30,240 --> 00:01:37,140
We can define one struct that manages approval of any of these types. In the struct definition,
28
00:01:37,210 --> 00:01:38,260
after we put the struct
29
00:01:38,270 --> 00:01:40,310
keyword and the name of the struct, Approval,
30
00:01:40,570 --> 00:01:44,680
we're going to put angle brackets.
31
00:01:44,690 --> 00:01:47,150
Inside the angle brackets, we declare the name of a generic type parameter.
32
00:01:47,640 --> 00:01:49,450
T is a common name for one.
33
00:01:51,040 --> 00:01:52,490
Within the struct definition,
34
00:01:52,500 --> 00:01:57,350
we can now use T to stand in for any concrete type we want to be able to use.
35
00:01:58,440 --> 00:01:59,260
In this case,
36
00:01:59,270 --> 00:02:13,125
we're going to have a field named item that has the generic type T, as well as a field named approval of type Boolean that keeps track of whether this item has been approved or not. To use generic type parameters in implementation blocks of structs,
37
00:02:13,245 --> 00:02:17,255
we need to declare the generic type in angle brackets after the impl keyword.
38
00:02:18,345 --> 00:02:21,724
Then, we'll immediately use the generic type, T, in the name of the type
39
00:02:21,725 --> 00:02:25,115
we're implementing methods on, which in this case, is Approval of T.
40
00:02:26,695 --> 00:02:37,255
Any methods, or associated functions, defined on Approval of T that want to have arguments or return types that involve the type of item, can use T because it's been declared after the impl keyword.
41
00:02:38,345 --> 00:02:39,105
For example,
42
00:02:39,115 --> 00:02:50,655
here's a new associated function that takes an item of type T and returns an Approval of T with approved set to false. Methods or associative functions within this impl block
43
00:02:50,665 --> 00:02:55,055
don't have to use the generic type parameter if their parameters or return types don't need it.
44
00:02:56,045 --> 00:02:56,965
For example,
45
00:02:57,025 --> 00:03:00,395
here's a method named approve that takes a mutable reference to self,
46
00:03:00,605 --> 00:03:03,655
sets the approved field to true, and returns nothing.
47
00:03:05,145 --> 00:03:09,754
Say we have a requirement for a replace method that takes a different item and returns a new
48
00:03:09,755 --> 00:03:13,255
Approval instance with the same approved value and the new item.
49
00:03:14,245 --> 00:03:18,125
If we want to allow the replacement item to be a different type than the current item,
50
00:03:18,165 --> 00:03:20,155
we need a new generic type parameter.
51
00:03:21,185 --> 00:03:21,814
We've chosen
52
00:03:21,815 --> 00:03:22,565
U here,
53
00:03:22,645 --> 00:03:29,855
declared U within angle brackets after the method name, and then used U for the type of the other_item parameter and the return type.
54
00:03:30,945 --> 00:03:33,905
Because the U type is only relevant to the replace method,
55
00:03:34,135 --> 00:03:38,355
we declare U in the method's signature rather than at the start of the impl block.
56
00:03:39,945 --> 00:03:44,125
If we had wanted to require the other item to be the same type as the current item,
57
00:03:44,285 --> 00:03:47,055
we wouldn't declare a new parameter for the replace method.
58
00:03:47,545 --> 00:03:54,144
We'd use the same T that goes with the current instance of Approval.
59
00:03:54,145 --> 00:04:00,655
To round out the Approval type's functionality, we'll add a method named approved_item that takes a reference to self and returns an option of a reference to T.
60
00:04:02,252 --> 00:04:04,862
We talked about the Option enum in unit 3.
61
00:04:05,952 --> 00:04:08,232
Its definition also uses generics.
62
00:04:08,512 --> 00:04:11,462
The Some(T) variant can hold a value of any type.
63
00:04:12,512 --> 00:04:13,191
In this case,
64
00:04:13,192 --> 00:04:16,602
we're making use of Option and, in place of Option's generic type T,
65
00:04:16,722 --> 00:04:18,922
we're substituting a reference to Approval's
66
00:04:18,932 --> 00:04:20,032
generic type T.
67
00:04:21,592 --> 00:04:24,042
If the approved field on this instance is true,
68
00:04:24,062 --> 00:04:26,762
we'll return the Some variant holding a reference to the item.
69
00:04:27,792 --> 00:04:29,432
If the approved field is false,
70
00:04:29,442 --> 00:04:31,062
we'll return the None variant.
71
00:04:31,952 --> 00:04:34,842
Now the Approval struct prevents access to the inner item
72
00:04:34,852 --> 00:04:36,562
unless the item has been approved.
73
00:04:37,352 --> 00:04:39,862
This code by itself compiles successfully.
74
00:04:40,452 --> 00:04:48,462
The compiler does type checking, keeping in mind that T will be filled in with some value and make sure that all of the code using T is consistent with itself.
75
00:04:48,992 --> 00:04:53,062
Rust doesn't need to know what T will be to check internal type correctness.
76
00:04:54,552 --> 00:04:55,202
Next,
77
00:04:55,272 --> 00:05:00,962
let's write some code that uses this definition and substitutes concrete types for the generic type parameters.
78
00:05:02,452 --> 00:05:05,022
When we write a main function that uses Approval,
79
00:05:05,102 --> 00:05:06,421
we can create an approval with,
80
00:05:06,422 --> 00:05:06,761
say,
81
00:05:06,762 --> 00:05:09,162
an integer amount that is requested for budgeting.
82
00:05:09,692 --> 00:05:11,192
After the amount is approved,
83
00:05:11,312 --> 00:05:14,862
the approved_item method will return a reference to that integer amount.
84
00:05:16,452 --> 00:05:21,862
We can also create an approval instance with an IP address that may or may not be allowed to access something.
85
00:05:22,352 --> 00:05:24,172
When we approve the IP address,
86
00:05:24,182 --> 00:05:27,562
the approved_item method returns a reference to that IP address.
87
00:05:29,092 --> 00:05:31,671
This code compiles and runs, and what Rust does
88
00:05:31,672 --> 00:05:36,522
behind the scenes is generate the Approval struct's code with each data type used in place of T.
89
00:05:38,152 --> 00:05:49,471
Let's look at what happens if we do something that violates the generic type constraints when we use the definition. Recall that we're currently requiring the other item passed to the replace method to be the same type as the type
90
00:05:49,472 --> 00:05:50,662
in the current instance.
91
00:05:52,252 --> 00:05:55,291
If we try to call the replace method on the approval instance,
92
00:05:55,292 --> 00:06:01,062
holding an IP address, and pass an integer instead, we'll get a compile-time error that says that there are mismatched types.
93
00:06:01,602 --> 00:06:03,662
This instance uses an IP address,
94
00:06:03,672 --> 00:06:05,742
but we gave the replace method an integer.
95
00:06:06,446 --> 00:06:07,475
Even though the code
96
00:06:07,476 --> 00:06:10,555
defined with generic type parameters compiles, the code
97
00:06:10,556 --> 00:06:13,506
using the definitions doesn't conform to the constraints.
98
00:06:15,096 --> 00:06:18,806
There's much more to generic type parameters that we're not going to cover in this detour.
99
00:06:19,396 --> 00:06:22,906
If you're interested in topics such as how to add trait bounds to generic types,
100
00:06:23,396 --> 00:06:26,206
what the differences are between generic types and associated types,
101
00:06:26,696 --> 00:06:28,656
and how to specify a default concrete type
102
00:06:28,666 --> 00:06:29,406
for a generic type,
103
00:06:29,896 --> 00:06:34,016
check out the online Rust language documentation.
104
00:06:34,026 --> 00:06:38,655
In this module, we discussed that we're taking a detour through generic type parameters to use them as a stepping stone
105
00:06:38,656 --> 00:06:41,106
to better understand generic lifetime parameters.
106
00:06:42,096 --> 00:06:48,006
We saw that we can declare generic type parameters within angle brackets, and then use the generic types in definitions.
107
00:06:48,596 --> 00:06:57,336
The compiler verifies the internal consistency of definitions using generics. When we write code using definitions containing generic type parameters,
108
00:06:57,516 --> 00:06:59,256
we substitute concrete types,
109
00:06:59,376 --> 00:07:03,106
and the compiler verifies that the concrete types don't violate the constraints.
110
00:07:04,696 --> 00:07:05,296
Next,
111
00:07:05,356 --> 00:07:09,256
let's see how generic lifetime parameters are similar to generic type parameters.