1
1
% Generics
2
2
3
3
Sometimes, when writing a function or data type, we may want it to work for
4
- multiple types of arguments. For example, remember our ` OptionalInt ` type?
4
+ multiple types of arguments. Luckily, Rust has a feature that gives us a better
5
+ way: generics. Generics are called ‘parametric polymorphism’ in type theory,
6
+ which means that they are types or functions that have multiple forms (‘poly’
7
+ is multiple, ‘morph’ is form) over a given parameter (‘parametric’).
5
8
6
- ``` {rust}
7
- enum OptionalInt {
8
- Value(i32),
9
- Missing,
10
- }
11
- ```
12
-
13
- If we wanted to also have an ` OptionalFloat64 ` , we would need a new enum:
14
-
15
- ``` {rust}
16
- enum OptionalFloat64 {
17
- Valuef64(f64),
18
- Missingf64,
19
- }
20
- ```
21
-
22
- This is really unfortunate. Luckily, Rust has a feature that gives us a better
23
- way: generics. Generics are called * parametric polymorphism* in type theory,
24
- which means that they are types or functions that have multiple forms (* poly*
25
- is multiple, * morph* is form) over a given parameter (* parametric* ).
26
-
27
- Anyway, enough with type theory declarations, let's check out the generic form
28
- of ` OptionalInt ` . It is actually provided by Rust itself, and looks like this:
9
+ Anyway, enough with type theory, let’s check out some generic code. Rust’s
10
+ standard library provides a type, ` Option<T> ` , that’s generic:
29
11
30
12
``` rust
31
13
enum Option <T > {
@@ -34,59 +16,109 @@ enum Option<T> {
34
16
}
35
17
```
36
18
37
- The ` <T> ` part, which you' ve seen a few times before, indicates that this is
19
+ The ` <T> ` part, which you’ ve seen a few times before, indicates that this is
38
20
a generic data type. Inside the declaration of our enum, wherever we see a ` T ` ,
39
- we substitute that type for the same type used in the generic. Here' s an
21
+ we substitute that type for the same type used in the generic. Here’ s an
40
22
example of using ` Option<T> ` , with some extra type annotations:
41
23
42
- ``` { rust}
24
+ ``` rust
43
25
let x : Option <i32 > = Some (5 );
44
26
```
45
27
46
28
In the type declaration, we say ` Option<i32> ` . Note how similar this looks to
47
29
` Option<T> ` . So, in this particular ` Option ` , ` T ` has the value of ` i32 ` . On
48
30
the right-hand side of the binding, we do make a ` Some(T) ` , where ` T ` is ` 5 ` .
49
- Since that' s an ` i32 ` , the two sides match, and Rust is happy. If they didn' t
50
- match, we' d get an error:
31
+ Since that’ s an ` i32 ` , the two sides match, and Rust is happy. If they didn’ t
32
+ match, we’ d get an error:
51
33
52
- ``` { rust,ignore}
34
+ ``` rust,ignore
53
35
let x: Option<f64> = Some(5);
54
36
// error: mismatched types: expected `core::option::Option<f64>`,
55
37
// found `core::option::Option<_>` (expected f64 but found integral variable)
56
38
```
57
39
58
- That doesn' t mean we can' t make ` Option<T> ` s that hold an ` f64 ` ! They just have to
59
- match up:
40
+ That doesn’ t mean we can’ t make ` Option<T> ` s that hold an ` f64 ` ! They just have
41
+ to match up:
60
42
61
- ``` { rust}
43
+ ``` rust
62
44
let x : Option <i32 > = Some (5 );
63
45
let y : Option <f64 > = Some (5.0f64 );
64
46
```
65
47
66
48
This is just fine. One definition, multiple uses.
67
49
68
- Generics don't have to only be generic over one type. Consider Rust's built-in
69
- ` Result<T, E> ` type:
50
+ Generics don’t have to only be generic over one type. Consider another type from Rust’s standard library that’s similar, ` Result<T, E> ` :
70
51
71
- ``` { rust}
52
+ ``` rust
72
53
enum Result <T , E > {
73
54
Ok (T ),
74
55
Err (E ),
75
56
}
76
57
```
77
58
78
59
This type is generic over _ two_ types: ` T ` and ` E ` . By the way, the capital letters
79
- can be any letter you' d like. We could define ` Result<T, E> ` as:
60
+ can be any letter you’ d like. We could define ` Result<T, E> ` as:
80
61
81
- ``` { rust}
62
+ ``` rust
82
63
enum Result <A , Z > {
83
64
Ok (A ),
84
65
Err (Z ),
85
66
}
86
67
```
87
68
88
69
if we wanted to. Convention says that the first generic parameter should be
89
- ` T ` , for ' type,' and that we use ` E ` for ' error.' Rust doesn' t care, however.
70
+ ` T ` , for ‘ type’, and that we use ` E ` for ‘ error’. Rust doesn’ t care, however.
90
71
91
72
The ` Result<T, E> ` type is intended to be used to return the result of a
92
- computation, and to have the ability to return an error if it didn't work out.
73
+ computation, and to have the ability to return an error if it didn’t work out.
74
+
75
+ ## Generic functions
76
+
77
+ We can write functions that take generic types with a similar syntax:
78
+
79
+ ``` rust
80
+ fn takes_anything <T >(x : T ) {
81
+ // do something with x
82
+ }
83
+ ```
84
+
85
+ The syntax has two parts: the ` <T> ` says “this function is generic over one
86
+ type, ` T ` ”, and the ` x: T ` says “x has the type ` T ` .”
87
+
88
+ Multiple arguments can have the same generic type:
89
+
90
+ ``` rust
91
+ fn takes_two_of_the_same_things <T >(x : T , y : T ) {
92
+ // ...
93
+ }
94
+ ```
95
+
96
+ We could write a version that takes multiple types:
97
+
98
+ ``` rust
99
+ fn takes_two_things <T , U >(x : T , y : U ) {
100
+ // ...
101
+ }
102
+ ```
103
+
104
+ Generic functions are most useful with ‘trait bounds’, which we’ll cover in the
105
+ [ section on traits] [ traits ] .
106
+
107
+ [ traits ] : traits.html
108
+
109
+ ## Generic structs
110
+
111
+ You can store a generic type in a ` struct ` as well:
112
+
113
+ ```
114
+ struct Point<T> {
115
+ x: T,
116
+ y: T,
117
+ }
118
+
119
+ let int_origin = Point { x: 0, y: 0 };
120
+ let float_origin = Point { x: 0.0, y: 0.0 };
121
+ ```
122
+
123
+ Similarly to functions, the ` <T> ` is where we declare the generic parameters,
124
+ and we then use ` x: T ` in the type declaration, too.
0 commit comments