]>
Commit | Line | Data |
---|---|---|
cc61c64b XL |
1 | ## Advanced Types |
2 | ||
2c00a5a8 | 3 | The Rust type system has some features that we’ve mentioned in this book but |
83c7162d XL |
4 | haven’t yet discussed. We’ll start by discussing newtypes in general as we |
5 | examine why newtypes are useful as types. Then we’ll move on to type aliases, a | |
6 | feature similar to newtypes but with slightly different semantics. We’ll also | |
7 | discuss the `!` type and dynamically sized types. | |
cc61c64b | 8 | |
83c7162d XL |
9 | > Note: The next section assumes you’ve read the earlier section “The Newtype |
10 | > Pattern to Implement External Traits on External Types.” | |
2c00a5a8 | 11 | |
83c7162d | 12 | ### Using the Newtype Pattern for Type Safety and Abstraction |
2c00a5a8 | 13 | |
94b46f34 XL |
14 | The newtype pattern is useful for tasks beyond those we’ve discussed so far, |
15 | including statically enforcing that values are never confused and indicating | |
16 | the units of a value. You saw an example of using newtypes to indicate units in | |
17 | Listing 19-23: recall that the `Millimeters` and `Meters` structs wrapped `u32` | |
18 | values in a newtype. If we wrote a function with a parameter of type | |
19 | `Millimeters`, we couldn’t compile a program that accidentally tried to call | |
20 | that function with a value of type `Meters` or a plain `u32`. | |
83c7162d XL |
21 | |
22 | Another use of the newtype pattern is in abstracting away some implementation | |
23 | details of a type: the new type can expose a public API that is different from | |
24 | the API of the private inner type if we used the new type directly to restrict | |
25 | the available functionality, for example. | |
26 | ||
27 | Newtypes can also hide internal implementation. For example, we could provide a | |
2c00a5a8 XL |
28 | `People` type to wrap a `HashMap<i32, String>` that stores a person’s ID |
29 | associated with their name. Code using `People` would only interact with the | |
30 | public API we provide, such as a method to add a name string to the `People` | |
83c7162d XL |
31 | collection; that code wouldn’t need to know that we assign an `i32` ID to names |
32 | internally. The newtype pattern is a lightweight way to achieve encapsulation | |
33 | to hide implementation details, which we discussed in the “Encapsulation that | |
34 | Hides Implementation Details” section of Chapter 17. | |
cc61c64b | 35 | |
94b46f34 | 36 | ### Creating Type Synonyms with Type Aliases |
cc61c64b | 37 | |
83c7162d | 38 | Along with the newtype pattern, Rust provides the ability to declare a *type |
2c00a5a8 XL |
39 | alias* to give an existing type another name. For this we use the `type` |
40 | keyword. For example, we can create the alias `Kilometers` to `i32` like so: | |
cc61c64b XL |
41 | |
42 | ```rust | |
43 | type Kilometers = i32; | |
44 | ``` | |
45 | ||
83c7162d XL |
46 | Now, the alias `Kilometers` is a *synonym* for `i32`; unlike the `Millimeters` |
47 | and `Meters` types we created in Listing 19-23, `Kilometers` is not a separate, | |
48 | new type. Values that have the type `Kilometers` will be treated the same as | |
49 | values of type `i32`: | |
cc61c64b XL |
50 | |
51 | ```rust | |
52 | type Kilometers = i32; | |
53 | ||
54 | let x: i32 = 5; | |
55 | let y: Kilometers = 5; | |
56 | ||
57 | println!("x + y = {}", x + y); | |
58 | ``` | |
59 | ||
83c7162d XL |
60 | Because `Kilometers` and `i32` are the same type, we can add values of both |
61 | types and we can pass `Kilometers` values to functions that take `i32` | |
62 | parameters. However, using this method, we don’t get the type checking benefits | |
63 | that we get from the newtype pattern discussed earlier. | |
cc61c64b XL |
64 | |
65 | The main use case for type synonyms is to reduce repetition. For example, we | |
83c7162d | 66 | might have a lengthy type like this: |
cc61c64b XL |
67 | |
68 | ```rust,ignore | |
3b2f2976 | 69 | Box<Fn() + Send + 'static> |
cc61c64b XL |
70 | ``` |
71 | ||
83c7162d XL |
72 | Writing this lengthy type in function signatures and as type annotations all |
73 | over the code can be tiresome and error prone. Imagine having a project full of | |
74 | code like that in Listing 19-32. | |
cc61c64b XL |
75 | |
76 | ```rust | |
3b2f2976 | 77 | let f: Box<Fn() + Send + 'static> = Box::new(|| println!("hi")); |
cc61c64b | 78 | |
3b2f2976 | 79 | fn takes_long_type(f: Box<Fn() + Send + 'static>) { |
ff7c6d11 | 80 | // --snip-- |
cc61c64b XL |
81 | } |
82 | ||
3b2f2976 | 83 | fn returns_long_type() -> Box<Fn() + Send + 'static> { |
ff7c6d11 | 84 | // --snip-- |
cc61c64b XL |
85 | # Box::new(|| ()) |
86 | } | |
87 | ``` | |
88 | ||
2c00a5a8 | 89 | <span class="caption">Listing 19-32: Using a long type in many places</span> |
cc61c64b | 90 | |
83c7162d XL |
91 | A type alias makes this code more manageable by reducing the repetition. In |
92 | Listing 19-33, we’ve introduced an alias named `Thunk` for the verbose type and | |
93 | can replace all uses of the type with the shorter alias `Thunk`. | |
cc61c64b XL |
94 | |
95 | ```rust | |
3b2f2976 | 96 | type Thunk = Box<Fn() + Send + 'static>; |
cc61c64b XL |
97 | |
98 | let f: Thunk = Box::new(|| println!("hi")); | |
99 | ||
100 | fn takes_long_type(f: Thunk) { | |
ff7c6d11 | 101 | // --snip-- |
cc61c64b XL |
102 | } |
103 | ||
104 | fn returns_long_type() -> Thunk { | |
ff7c6d11 | 105 | // --snip-- |
cc61c64b XL |
106 | # Box::new(|| ()) |
107 | } | |
108 | ``` | |
109 | ||
2c00a5a8 | 110 | <span class="caption">Listing 19-33: Introducing a type alias `Thunk` to reduce |
cc61c64b XL |
111 | repetition</span> |
112 | ||
83c7162d XL |
113 | This code is much easier to read and write! Choosing a meaningful name for a |
114 | type alias can help communicate your intent as well (*thunk* is a word for code | |
115 | to be evaluated at a later time, so it’s an appropriate name for a closure that | |
116 | gets stored). | |
cc61c64b | 117 | |
2c00a5a8 XL |
118 | Type aliases are also commonly used with the `Result<T, E>` type for reducing |
119 | repetition. Consider the `std::io` module in the standard library. I/O | |
120 | operations often return a `Result<T, E>` to handle situations when operations | |
121 | fail to work. This library has a `std::io::Error` struct that represents all | |
122 | possible I/O errors. Many of the functions in `std::io` will be returning | |
123 | `Result<T, E>` where the `E` is `std::io::Error`, such as these functions in | |
124 | the `Write` trait: | |
cc61c64b XL |
125 | |
126 | ```rust | |
127 | use std::io::Error; | |
3b2f2976 | 128 | use std::fmt; |
cc61c64b XL |
129 | |
130 | pub trait Write { | |
131 | fn write(&mut self, buf: &[u8]) -> Result<usize, Error>; | |
132 | fn flush(&mut self) -> Result<(), Error>; | |
133 | ||
134 | fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>; | |
3b2f2976 | 135 | fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>; |
cc61c64b XL |
136 | } |
137 | ``` | |
138 | ||
83c7162d | 139 | The `Result<..., Error>` is repeated a lot. As such, `std::io` has this type of |
cc61c64b XL |
140 | alias declaration: |
141 | ||
142 | ```rust,ignore | |
143 | type Result<T> = Result<T, std::io::Error>; | |
144 | ``` | |
145 | ||
83c7162d | 146 | Because this declaration is in the `std::io` module, we can use the fully |
94b46f34 | 147 | qualified alias `std::io::Result<T>`—that is, a `Result<T, E>` with the `E` |
83c7162d XL |
148 | filled in as `std::io::Error`. The `Write` trait function signatures end up |
149 | looking like this: | |
cc61c64b XL |
150 | |
151 | ```rust,ignore | |
152 | pub trait Write { | |
153 | fn write(&mut self, buf: &[u8]) -> Result<usize>; | |
154 | fn flush(&mut self) -> Result<()>; | |
155 | ||
156 | fn write_all(&mut self, buf: &[u8]) -> Result<()>; | |
157 | fn write_fmt(&mut self, fmt: Arguments) -> Result<()>; | |
158 | } | |
159 | ``` | |
160 | ||
83c7162d XL |
161 | The type alias helps in two ways: it makes code easier to write *and* it gives |
162 | us a consistent interface across all of `std::io`. Because it’s an alias, it’s | |
163 | just another `Result<T, E>`, which means we can use any methods that work on | |
94b46f34 | 164 | `Result<T, E>` with it, as well as special syntax like the `?` operator. |
cc61c64b | 165 | |
94b46f34 | 166 | ### The Never Type that Never Returns |
cc61c64b | 167 | |
2c00a5a8 | 168 | Rust has a special type named `!` that’s known in type theory lingo as the |
83c7162d | 169 | *empty type* because it has no values. We prefer to call it the *never type* |
2c00a5a8 | 170 | because it stands in the place of the return type when a function will never |
83c7162d | 171 | return. Here is an example: |
cc61c64b XL |
172 | |
173 | ```rust,ignore | |
174 | fn bar() -> ! { | |
ff7c6d11 | 175 | // --snip-- |
3b2f2976 | 176 | } |
cc61c64b XL |
177 | ``` |
178 | ||
83c7162d XL |
179 | This code is read as “the function `bar` returns never.” Functions that return |
180 | never are called *diverging functions*. We can’t create values of the type `!` | |
181 | so `bar` can never possibly return. | |
2c00a5a8 | 182 | |
83c7162d | 183 | But what use is a type you can never create values for? Recall the code from |
94b46f34 | 184 | Listing 2-5; we’ve reproduced part of it here in Listing 19-34. |
cc61c64b XL |
185 | |
186 | ```rust | |
187 | # let guess = "3"; | |
188 | # loop { | |
189 | let guess: u32 = match guess.trim().parse() { | |
190 | Ok(num) => num, | |
191 | Err(_) => continue, | |
192 | }; | |
193 | # break; | |
194 | # } | |
195 | ``` | |
196 | ||
2c00a5a8 | 197 | <span class="caption">Listing 19-34: A `match` with an arm that ends in |
cc61c64b XL |
198 | `continue`</span> |
199 | ||
2c00a5a8 | 200 | At the time, we skipped over some details in this code. In Chapter 6 in “The |
83c7162d XL |
201 | `match` Control Flow Operator” section, we discussed that `match` arms must all |
202 | return the same type. So, for example, the following code doesn’t work: | |
cc61c64b XL |
203 | |
204 | ```rust,ignore | |
83c7162d | 205 | let guess = match guess.trim().parse() { |
cc61c64b XL |
206 | Ok(_) => 5, |
207 | Err(_) => "hello", | |
208 | } | |
209 | ``` | |
210 | ||
83c7162d | 211 | The type of `guess` in this code would have to be an integer *and* a string, |
94b46f34 | 212 | and Rust requires that `guess` have only one type. So what does `continue` |
2c00a5a8 XL |
213 | return? How were we allowed to return a `u32` from one arm and have another arm |
214 | that ends with `continue` in Listing 19-34? | |
cc61c64b | 215 | |
83c7162d XL |
216 | As you might have guessed, `continue` has a `!` value. That is, when Rust |
217 | computes the type of `guess`, it looks at both match arms, the former with a | |
218 | value of `u32` and the latter with a `!` value. Because `!` can never have a | |
219 | value, Rust decides that the type of `guess` is `u32`. | |
2c00a5a8 XL |
220 | |
221 | The formal way of describing this behavior is that expressions of type `!` can | |
222 | be coerced into any other type. We’re allowed to end this `match` arm with | |
83c7162d XL |
223 | `continue` because `continue` doesn’t return a value; instead, it moves control |
224 | back to the top of the loop, so in the `Err` case, we never assign a value to | |
225 | `guess`. | |
226 | ||
227 | The never type is useful with the `panic!` macro as well. Remember the `unwrap` | |
228 | function that we call on `Option<T>` values to produce a value or panic? Here | |
229 | is its definition: | |
cc61c64b XL |
230 | |
231 | ```rust,ignore | |
232 | impl<T> Option<T> { | |
233 | pub fn unwrap(self) -> T { | |
234 | match self { | |
235 | Some(val) => val, | |
236 | None => panic!("called `Option::unwrap()` on a `None` value"), | |
237 | } | |
238 | } | |
239 | } | |
240 | ``` | |
241 | ||
83c7162d | 242 | In this code, the same thing happens as in the `match` in Listing 19-34: Rust |
94b46f34 XL |
243 | sees that `val` has the type `T` and `panic!` has the type `!`, so the result |
244 | of the overall `match` expression is `T`. This code works because `panic!` | |
245 | doesn’t produce a value; it ends the program. In the `None` case, we won’t be | |
246 | returning a value from `unwrap`, so this code is valid. | |
cc61c64b XL |
247 | |
248 | One final expression that has the type `!` is a `loop`: | |
249 | ||
250 | ```rust,ignore | |
251 | print!("forever "); | |
252 | ||
253 | loop { | |
254 | print!("and ever "); | |
255 | } | |
256 | ``` | |
257 | ||
83c7162d XL |
258 | Here, the loop never ends, so `!` is the value of the expression. However, this |
259 | wouldn’t be true if we included a `break`, because the loop would terminate | |
260 | when it got to the `break`. | |
cc61c64b | 261 | |
94b46f34 | 262 | ### Dynamically Sized Types and the `Sized` Trait |
cc61c64b | 263 | |
83c7162d XL |
264 | Due to Rust’s need to know certain details, such as how much space to allocate |
265 | for a value of a particular type, there is a corner of its type system that can | |
266 | be confusing: the concept of *dynamically sized types*. Sometimes referred to | |
267 | as *DSTs* or *unsized types*, these types let us write code using values whose | |
94b46f34 | 268 | size we can know only at runtime. |
cc61c64b | 269 | |
83c7162d XL |
270 | Let’s dig into the details of a dynamically sized type called `str`, which |
271 | we’ve been using throughout the book. That’s right, not `&str`, but `str` on | |
272 | its own, is a DST. We can’t know how long the string is until runtime, meaning | |
273 | we can’t create a variable of type `str`, nor can we take an argument of type | |
274 | `str`. Consider the following code, which does not work: | |
cc61c64b XL |
275 | |
276 | ```rust,ignore | |
277 | let s1: str = "Hello there!"; | |
278 | let s2: str = "How's it going?"; | |
279 | ``` | |
280 | ||
2c00a5a8 | 281 | Rust needs to know how much memory to allocate for any value of a particular |
83c7162d XL |
282 | type, and all values of a type must use the same amount of memory. If Rust |
283 | allowed us to write this code, these two `str` values would need to take up the | |
284 | same amount of space. But they have different lengths: `s1` needs 12 bytes of | |
285 | storage and `s2` needs 15. This is why it’s not possible to create a variable | |
286 | holding a dynamically sized type. | |
287 | ||
288 | So what do we do? In this case, you already know the answer: we make the types | |
289 | of `s1` and `s2` a `&str` rather than a `str`. Recall that in the “String | |
94b46f34 | 290 | Slices” section of Chapter 4, we said the slice data structure stores the |
2c00a5a8 | 291 | starting position and the length of the slice. |
cc61c64b | 292 | |
83c7162d XL |
293 | So although a `&T` is a single value that stores the memory address of where |
294 | the `T` is located, a `&str` is *two* values: the address of the `str` and its | |
295 | length. As such, we can know the size of a `&str` value at compile time: it’s | |
94b46f34 XL |
296 | twice the length of a `usize`. That is, we always know the size of a `&str`, no |
297 | matter how long the string it refers to is. In general, this is the way in | |
298 | which dynamically sized types are used in Rust: they have an extra bit of | |
299 | metadata that stores the size of the dynamic information. The golden rule of | |
300 | dynamically sized types is that we must always put values of dynamically sized | |
301 | types behind a pointer of some kind. | |
2c00a5a8 | 302 | |
83c7162d XL |
303 | We can combine `str` with all kinds of pointers: for example, `Box<str>` or |
304 | `Rc<str>`. In fact, you’ve seen this before but with a different dynamically | |
2c00a5a8 XL |
305 | sized type: traits. Every trait is a dynamically sized type we can refer to by |
306 | using the name of the trait. In Chapter 17 in the “Using Trait Objects that | |
83c7162d XL |
307 | Allow for Values of Different Types” section, we mentioned that to use traits |
308 | as trait objects, we must put them behind a pointer, such as `&Trait` or | |
309 | `Box<Trait>` (`Rc<Trait>` would work too). | |
2c00a5a8 | 310 | |
83c7162d XL |
311 | To work with DSTs, Rust has a particular trait called the `Sized` trait to |
312 | determine whether or not a type’s size is known at compile time. This trait is | |
313 | automatically implemented for everything whose size is known at compile time. | |
314 | In addition, Rust implicitly adds a bound on `Sized` to every generic function. | |
315 | That is, a generic function definition like this: | |
cc61c64b XL |
316 | |
317 | ```rust,ignore | |
318 | fn generic<T>(t: T) { | |
ff7c6d11 | 319 | // --snip-- |
3b2f2976 | 320 | } |
cc61c64b XL |
321 | ``` |
322 | ||
83c7162d | 323 | is actually treated as though we had written this: |
cc61c64b XL |
324 | |
325 | ```rust,ignore | |
326 | fn generic<T: Sized>(t: T) { | |
ff7c6d11 | 327 | // --snip-- |
3b2f2976 | 328 | } |
cc61c64b XL |
329 | ``` |
330 | ||
94b46f34 | 331 | By default, generic functions will work only on types that have a known size at |
83c7162d | 332 | compile time. However, you can use the following special syntax to relax this |
cc61c64b XL |
333 | restriction: |
334 | ||
335 | ```rust,ignore | |
336 | fn generic<T: ?Sized>(t: &T) { | |
ff7c6d11 | 337 | // --snip-- |
3b2f2976 | 338 | } |
cc61c64b XL |
339 | ``` |
340 | ||
83c7162d XL |
341 | A trait bound on `?Sized` is the opposite of a trait bound on `Sized`: we would |
342 | read this as “`T` may or may not be `Sized`.” This syntax is only available for | |
343 | `Sized`, not any other traits. | |
cc61c64b | 344 | |
83c7162d XL |
345 | Also note that we switched the type of the `t` parameter from `T` to `&T`. |
346 | Because the type might not be `Sized`, we need to use it behind some kind of | |
347 | pointer. In this case, we’ve chosen a reference. | |
cc61c64b | 348 | |
83c7162d | 349 | Next, we’ll talk about functions and closures! |