1 00:00:00,120 --> 00:00:02,200 Welcome to module 6 on custom 2 00:00:02,400 --> 00:00:03,030 error types. 3 00:00:04,490 --> 00:00:06,309 In this module, we're going to cover 4 00:00:06,509 --> 00:00:08,209 two solutions to the problem of a 5 00:00:08,409 --> 00:00:10,090 function that might fail in multiple 6 00:00:10,290 --> 00:00:10,710 ways. 7 00:00:11,620 --> 00:00:13,300 We'll first talk about how 8 00:00:13,500 --> 00:00:15,340 and why to use Box 9 00:00:15,540 --> 00:00:17,620 as the error type of a function-returning 10 00:00:17,820 --> 00:00:17,950 result. 11 00:00:18,950 --> 00:00:20,809 Then, we'll talk about when 12 00:00:21,009 --> 00:00:22,370 Box might not be sufficient, 13 00:00:22,820 --> 00:00:24,529 and how to use a custom error type 14 00:00:24,729 --> 00:00:25,040 instead. 15 00:00:26,050 --> 00:00:27,700 When using a custom error type, 16 00:00:28,120 --> 00:00:30,100 you'll need to implement the Error trait. 17 00:00:30,610 --> 00:00:32,449 Implementing the From trait will let 18 00:00:32,649 --> 00:00:34,120 you use the ? operator. 19 00:00:34,930 --> 00:00:36,430 We'll show you how to implement those. 20 00:00:37,380 --> 00:00:39,210 Finally, we'll show a common 21 00:00:39,410 --> 00:00:41,250 idiom defining a Result 22 00:00:41,450 --> 00:00:43,710 type alias that uses your custom error. 23 00:00:45,220 --> 00:00:45,970 Let's get started 24 00:00:46,170 --> 00:00:47,030 with how and why 25 00:00:47,230 --> 00:00:48,340 to return Box. 26 00:00:49,840 --> 00:00:52,030 In the module on advantages of Rust's 27 00:00:52,230 --> 00:00:53,260 error handling strategy, 28 00:00:53,770 --> 00:00:55,689 we ended with a function that reads an 29 00:00:55,889 --> 00:00:57,660 environment variable that parses 30 00:00:57,860 --> 00:00:59,740 the environment variable string value into a 31 00:00:59,940 --> 00:01:01,590 number and adds 1 to it. 32 00:01:02,590 --> 00:01:04,959 We want to use the ? operator 33 00:01:05,170 --> 00:01:06,500 to propagate the errors, 34 00:01:07,240 --> 00:01:09,790 but the problem is each of these operations 35 00:01:09,990 --> 00:01:11,320 returns a different error type. 36 00:01:11,800 --> 00:01:13,779 Therefore, we can't use either 37 00:01:13,979 --> 00:01:15,760 of those types as the error type 38 00:01:15,960 --> 00:01:16,720 for our function. 39 00:01:17,470 --> 00:01:18,640 So, what can we use? 40 00:01:19,640 --> 00:01:21,560 One solution is specifying 41 00:01:21,760 --> 00:01:23,480 the trait object, Box, 42 00:01:23,960 --> 00:01:25,189 using the Error trait 43 00:01:25,389 --> 00:01:26,320 from the Standard Library. 44 00:01:27,790 --> 00:01:29,919 Let's talk in detail about what 45 00:01:30,119 --> 00:01:30,820 Box means. 46 00:01:31,300 --> 00:01:33,309 You might see this written as 47 00:01:33,509 --> 00:01:35,169 Box in versions 48 00:01:35,369 --> 00:01:37,060 of Rust greater than 1.27 - 49 00:01:37,570 --> 00:01:38,410 they're the same thing. 50 00:01:39,400 --> 00:01:41,319 This is the simplest solution to 51 00:01:41,519 --> 00:01:43,179 the problem we had in the num_threads 52 00:01:43,379 --> 00:01:45,069 function. The function will now 53 00:01:45,269 --> 00:01:45,700 compile. 54 00:01:46,710 --> 00:01:48,570 Box is a trait object. 55 00:01:49,240 --> 00:01:51,350 A trait object consists of a pointer, 56 00:01:51,720 --> 00:01:53,699 in this case the Box type, which points 57 00:01:53,899 --> 00:01:55,610 to data on the heap; and 58 00:01:55,810 --> 00:01:56,100 a trait, 59 00:01:56,610 --> 00:01:58,680 in this case, the Standard Library's Error trait. 60 00:01:59,670 --> 00:02:01,590 See The Rust Programming Language book, 61 00:02:01,790 --> 00:02:03,550 chapter 17, for more 62 00:02:03,750 --> 00:02:05,730 information on trait objects in general. 63 00:02:07,230 --> 00:02:09,059 What this type means is that this 64 00:02:09,259 --> 00:02:11,008 function will return some type that 65 00:02:11,208 --> 00:02:12,300 implements the Error trait, 66 00:02:12,930 --> 00:02:15,020 but the program doesn't know what the concrete type 67 00:02:15,280 --> 00:02:16,800 will be until runtime. 68 00:02:18,200 --> 00:02:20,330 The Error trait requires the Debug 69 00:02:20,530 --> 00:02:21,500 and Display traits. 70 00:02:21,890 --> 00:02:23,830 So, what functions that call this function 71 00:02:24,030 --> 00:02:26,150 know is that the error value returned 72 00:02:26,660 --> 00:02:28,639 will be something that can be printed either 73 00:02:28,839 --> 00:02:29,540 to a developer 74 00:02:29,740 --> 00:02:30,560 or an end user. 75 00:02:32,040 --> 00:02:33,899 For example, here's the main 76 00:02:34,099 --> 00:02:35,790 function for a command-line program 77 00:02:36,080 --> 00:02:38,399 that has a wrapper for the entire application's 78 00:02:38,599 --> 00:02:40,470 functionality - a function called 79 00:02:40,670 --> 00:02:41,610 run_application. 80 00:02:42,600 --> 00:02:44,579 run_application calls the 81 00:02:44,779 --> 00:02:45,480 num_threads function 82 00:02:45,720 --> 00:02:47,730 and anything else the application does. 83 00:02:48,230 --> 00:02:50,010 If num_threads returns an error 84 00:02:50,210 --> 00:02:51,270 that gets propagated up, 85 00:02:51,930 --> 00:02:52,760 main panics 86 00:02:52,960 --> 00:02:54,660 with a message that displays the error, 87 00:02:54,860 --> 00:02:55,470 then exits. 88 00:02:56,450 --> 00:02:57,529 For an application 89 00:02:57,729 --> 00:02:59,449 with an outer layer that handles all 90 00:02:59,649 --> 00:03:00,620 errors by printing them for 91 00:03:00,820 --> 00:03:02,539 the end user, the 92 00:03:02,739 --> 00:03:04,640 Box strategy is all you need. 93 00:03:06,130 --> 00:03:08,320 The main downside to using Box 94 00:03:08,740 --> 00:03:10,989 is that code that calls a function returning 95 00:03:11,189 --> 00:03:12,879 Box can't see the 96 00:03:13,079 --> 00:03:14,620 actual type that was returned 97 00:03:14,920 --> 00:03:16,749 and use that information to do something 98 00:03:16,949 --> 00:03:17,110 different. 99 00:03:18,620 --> 00:03:20,629 If that's the case, you should instead 100 00:03:20,829 --> 00:03:21,890 use a custom error type. 101 00:03:22,460 --> 00:03:23,239 Let's look at how 102 00:03:23,439 --> 00:03:24,150 and why to do that. 103 00:03:25,580 --> 00:03:27,490 For this example, we're going to 104 00:03:27,690 --> 00:03:29,420 implement a tiny part of a 105 00:03:29,620 --> 00:03:31,789 document storage service, creating 106 00:03:31,989 --> 00:03:32,520 a new document. 107 00:03:33,590 --> 00:03:36,050 The create_document function we're implementing 108 00:03:36,410 --> 00:03:38,720 will take the name of the document it should create 109 00:03:39,290 --> 00:03:41,200 and will return a file for the color to 110 00:03:41,400 --> 00:03:43,150 write to if everything goes well. 111 00:03:44,140 --> 00:03:46,090 As implementers of the service, 112 00:03:46,420 --> 00:03:48,249 we want to prevent abuse by 113 00:03:48,449 --> 00:03:50,169 adding a rate limit to set how many 114 00:03:50,369 --> 00:03:51,580 documents someone can create 115 00:03:51,780 --> 00:03:52,000 per minute. 116 00:03:52,820 --> 00:03:55,070 This function should also return an error 117 00:03:55,370 --> 00:03:57,290 if a document of the specified name already 118 00:03:57,700 --> 00:03:58,470 exists. 119 00:03:59,990 --> 00:04:01,750 Here's a first attempt at 120 00:04:01,950 --> 00:04:03,880 implementing the create_document function 121 00:04:04,180 --> 00:04:05,170 using Box. 122 00:04:06,550 --> 00:04:08,430 We're not going to actually implement 123 00:04:08,630 --> 00:04:09,210 the rate limiting 124 00:04:09,410 --> 00:04:10,170 in this example. 125 00:04:10,650 --> 00:04:12,990 We've hardcoded a constant for the maximum 126 00:04:13,190 --> 00:04:15,029 and the return value for a function that would 127 00:04:15,229 --> 00:04:16,860 return the actual number of documents 128 00:04:17,060 --> 00:04:18,240 created in the last minute. 129 00:04:18,720 --> 00:04:20,590 You can change these values to simulate 130 00:04:20,790 --> 00:04:22,770 being over or under the rate limit 131 00:04:23,040 --> 00:04:24,959 if you want to see how this code behaves in 132 00:04:25,159 --> 00:04:25,740 those cases. 133 00:04:27,230 --> 00:04:29,330 The body of the create_document function 134 00:04:29,580 --> 00:04:31,640 checks to see if the caller is over the rate 135 00:04:31,840 --> 00:04:33,829 limit and, if so, returns 136 00:04:34,029 --> 00:04:34,969 a string turned into a 137 00:04:35,169 --> 00:04:35,660 Box. 138 00:04:36,650 --> 00:04:38,720 This is possible because of a From trait 139 00:04:38,920 --> 00:04:40,939 implementation provided by the Standard 140 00:04:41,139 --> 00:04:41,480 Library. 141 00:04:41,990 --> 00:04:43,849 We'll be talking about the From trait later in 142 00:04:44,049 --> 00:04:44,450 this module. 143 00:04:45,430 --> 00:04:46,020 Next, 144 00:04:46,300 --> 00:04:48,280 the function opens a file for writing 145 00:04:48,480 --> 00:04:50,240 with the create_new option set 146 00:04:50,640 --> 00:04:52,600 that will return an error if the file 147 00:04:52,800 --> 00:04:53,650 already exists. 148 00:04:55,130 --> 00:04:56,540 If everything goes well, 149 00:04:56,900 --> 00:04:58,820 this function returns a writable file 150 00:04:59,020 --> 00:04:59,210 handle. 151 00:05:00,190 --> 00:05:01,420 This function compiles 152 00:05:01,620 --> 00:05:03,040 and does what it's supposed to do. 153 00:05:03,460 --> 00:05:05,499 However, the way it's written makes 154 00:05:05,699 --> 00:05:07,510 it hard for a caller to use this function. 155 00:05:08,990 --> 00:05:10,819 For example, a programmer 156 00:05:11,019 --> 00:05:12,919 writing an application that uses 157 00:05:13,119 --> 00:05:14,870 this document creation service API 158 00:05:15,510 --> 00:05:17,090 wants to wait and try again 159 00:05:17,290 --> 00:05:18,830 if the rate limit has been exceeded. 160 00:05:19,830 --> 00:05:22,140 If a document with that name already exists, 161 00:05:22,450 --> 00:05:24,660 they'd like to add a number to the end of the name 162 00:05:25,140 --> 00:05:27,029 rather than asking the end user to pick 163 00:05:27,229 --> 00:05:27,660 a different name. 164 00:05:28,660 --> 00:05:30,660 So, the code calling the create_document 165 00:05:30,860 --> 00:05:33,009 function needs to be able to distinguish 166 00:05:33,209 --> 00:05:34,840 the different failure causes, not just print 167 00:05:35,040 --> 00:05:35,890 out their error. 168 00:05:37,410 --> 00:05:39,369 With the create_document function as 169 00:05:39,569 --> 00:05:39,830 written, 170 00:05:40,410 --> 00:05:41,669 there are complex 171 00:05:41,869 --> 00:05:43,890 and brittle ways callers could distinguish 172 00:05:44,090 --> 00:05:45,959 the file creation errors from the string 173 00:05:46,159 --> 00:05:46,350 error, 174 00:05:46,860 --> 00:05:48,750 and verify that the string error text 175 00:05:48,950 --> 00:05:50,520 is that of the error they're looking for. 176 00:05:50,970 --> 00:05:52,889 But there's a friendlier way we could write this 177 00:05:53,089 --> 00:05:54,749 function - using a custom 178 00:05:54,949 --> 00:05:55,260 error type. 179 00:05:56,750 --> 00:05:57,769 You can use a struct 180 00:05:57,969 --> 00:05:58,520 or an enum. 181 00:05:59,120 --> 00:06:01,249 An enum is more common because the type 182 00:06:01,449 --> 00:06:03,139 enumerates all the ways your code 183 00:06:03,339 --> 00:06:03,680 might fail. 184 00:06:04,160 --> 00:06:06,079 This is friendlier because callers can 185 00:06:06,279 --> 00:06:08,359 use a match expression to distinguish 186 00:06:08,559 --> 00:06:09,710 between different errors. 187 00:06:11,210 --> 00:06:13,039 So, to change our example from 188 00:06:13,239 --> 00:06:15,020 using Box to using 189 00:06:15,220 --> 00:06:17,149 a custom error type, we're first 190 00:06:17,349 --> 00:06:19,100 going to define an enum named 191 00:06:19,300 --> 00:06:21,170 DocumentServiceError with variants 192 00:06:21,590 --> 00:06:23,420 for all the reasons our code might fail, 193 00:06:23,810 --> 00:06:25,810 which is currently RateLimitExceeded 194 00:06:26,010 --> 00:06:26,660 and Io. 195 00:06:28,140 --> 00:06:30,150 The Io variant will hold the io error 196 00:06:30,350 --> 00:06:32,330 instance we get from performing 197 00:06:32,530 --> 00:06:33,660 IO operations. 198 00:06:38,140 --> 00:06:40,150 Custom error types should have the Error 199 00:06:40,350 --> 00:06:41,680 trait implemented on them. 200 00:06:41,950 --> 00:06:42,940 So, let's do that now. 201 00:06:44,420 --> 00:06:46,500 If you're using Rust 1.24. 202 00:06:46,700 --> 00:06:48,769 1, as we are, implementing 203 00:06:48,969 --> 00:06:51,350 the Error trait involves adding a definition 204 00:06:51,550 --> 00:06:52,640 with a description method. 205 00:06:54,100 --> 00:06:55,500 In Rust 1.27, 206 00:06:55,780 --> 00:06:57,700 the description method on the Error trait 207 00:06:57,900 --> 00:06:58,750 became optional. 208 00:06:59,200 --> 00:07:01,089 So, if you're using a version of Rust newer 209 00:07:01,289 --> 00:07:03,249 than 1.27, the implementation 210 00:07:03,449 --> 00:07:04,510 block can be empty. 211 00:07:07,030 --> 00:07:08,800 The Error trait requires that the type 212 00:07:09,000 --> 00:07:10,510 also implements the Debug 213 00:07:10,710 --> 00:07:12,369 and Display traits, so 214 00:07:12,569 --> 00:07:13,750 we're going to derive Debug 215 00:07:14,080 --> 00:07:16,390 and implement user-friendly error messages 216 00:07:16,590 --> 00:07:17,200 for Display. 217 00:07:19,510 --> 00:07:21,339 Next, we're going to change the return 218 00:07:21,539 --> 00:07:23,560 type of the create_document function 219 00:07:24,010 --> 00:07:26,139 to use DocumentServiceError instead 220 00:07:26,339 --> 00:07:26,920 of Box. 221 00:07:28,460 --> 00:07:30,350 Then, we change the rate limit case 222 00:07:30,550 --> 00:07:32,449 to create an instance of the 223 00:07:32,649 --> 00:07:33,330 RateLimit Exceeded variant 224 00:07:33,530 --> 00:07:34,190 instead of a string. 225 00:07:35,660 --> 00:07:37,579 In the spot where we use the ? 226 00:07:37,779 --> 00:07:39,409 operator, we can leave the 227 00:07:39,609 --> 00:07:41,239 code in the create_document function the 228 00:07:41,439 --> 00:07:41,720 same, 229 00:07:42,170 --> 00:07:43,780 if we implement the From trait. 230 00:07:44,560 --> 00:07:45,910 Let's look at how to do that now. 231 00:07:47,430 --> 00:07:49,379 The From trait is for converting one 232 00:07:49,579 --> 00:07:50,700 type into another type. 233 00:07:51,180 --> 00:07:52,980 It's useful in many situations. 234 00:07:53,280 --> 00:07:55,500 The situation we're interested in right now 235 00:07:55,890 --> 00:07:57,510 is that the ? operator 236 00:07:57,840 --> 00:07:59,699 will look for an implementation of the 237 00:07:59,899 --> 00:08:01,860 From trait to convert an error 238 00:08:02,060 --> 00:08:03,780 of one type into the return 239 00:08:03,980 --> 00:08:04,320 error type. 240 00:08:05,840 --> 00:08:07,759 So, in order to use the ? in 241 00:08:07,959 --> 00:08:09,920 our example, we need to implement 242 00:08:10,120 --> 00:08:11,840 the From trait to convert from 243 00:08:12,040 --> 00:08:12,710 io::Error 244 00:08:13,130 --> 00:08:14,900 and create a DocumentServiceError. 245 00:08:15,860 --> 00:08:17,879 The function we have to implement is also 246 00:08:18,079 --> 00:08:19,709 called from, takes an 247 00:08:19,909 --> 00:08:21,010 io::Error parameter 248 00:08:21,210 --> 00:08:22,330 that we're going to give the name 249 00:08:22,530 --> 00:08:24,660 other, and returns an instance 250 00:08:24,860 --> 00:08:26,040 of DocumentServiceError, 251 00:08:26,580 --> 00:08:28,140 which we can also write as Self. 252 00:08:29,100 --> 00:08:30,480 In the body of the function, 253 00:08:30,840 --> 00:08:32,519 we're going to wrap the io::Error 254 00:08:32,719 --> 00:08:34,499 in an instance of the DocumentServiceError 255 00:08:34,699 --> 00:08:35,860 Io variant. 256 00:08:36,870 --> 00:08:38,729 Now, anywhere that we have an 257 00:08:38,929 --> 00:08:40,530 io::Error and want to return 258 00:08:40,730 --> 00:08:41,819 a DocumentServiceError, 259 00:08:42,299 --> 00:08:44,158 the ? operator will take care 260 00:08:44,358 --> 00:08:45,360 of the conversion for us. 261 00:08:46,860 --> 00:08:48,480 This example compiles 262 00:08:48,680 --> 00:08:50,489 and we've made it easier for callers to 263 00:08:50,689 --> 00:08:52,409 distinguish between different ways that 264 00:08:52,609 --> 00:08:54,239 our code fails so that they 265 00:08:54,439 --> 00:08:55,620 can take different actions. 266 00:08:57,140 --> 00:08:59,060 Finally, let's improve this code 267 00:08:59,260 --> 00:09:00,770 with the Result type alias. 268 00:09:02,270 --> 00:09:04,109 If our crate has many functions that 269 00:09:04,309 --> 00:09:06,089 use the DocumentServiceError as 270 00:09:06,289 --> 00:09:07,989 their error type, we 271 00:09:08,189 --> 00:09:10,270 can reduce the repetition by defining 272 00:09:10,470 --> 00:09:11,680 an alias for the Result type 273 00:09:12,120 --> 00:09:14,049 within our crate that always 274 00:09:14,249 --> 00:09:15,880 uses DocumentServiceError as its error 275 00:09:16,080 --> 00:09:16,190 type. 276 00:09:17,710 --> 00:09:19,210 This Result, as defined, 277 00:09:19,630 --> 00:09:21,660 will still be generic in the success type, 278 00:09:22,270 --> 00:09:23,860 so we still have to specify that 279 00:09:24,060 --> 00:09:24,940 with each usage, 280 00:09:25,270 --> 00:09:27,129 but the error type no longer needs to be 281 00:09:27,329 --> 00:09:27,580 written out. 282 00:09:29,090 --> 00:09:31,030 In this module, we examined two 283 00:09:31,230 --> 00:09:32,149 solutions for dealing 284 00:09:32,349 --> 00:09:34,069 with functions that return multiple error 285 00:09:34,269 --> 00:09:34,550 types. 286 00:09:35,560 --> 00:09:37,000 Returning the Box trait 287 00:09:37,200 --> 00:09:39,069 object is sufficient in cases 288 00:09:39,269 --> 00:09:41,380 where you only need to know that an error happened 289 00:09:41,800 --> 00:09:43,750 and be able to print out error information. 290 00:09:44,740 --> 00:09:46,599 Defining a custom error type is 291 00:09:46,799 --> 00:09:48,429 recommended when callers need to be 292 00:09:48,629 --> 00:09:50,050 able to distinguish in code 293 00:09:50,290 --> 00:09:51,890 between different error causes. 294 00:09:52,950 --> 00:09:54,899 Custom error types should implement 295 00:09:55,099 --> 00:09:56,819 the Error trait, which requires 296 00:09:57,019 --> 00:09:58,919 the Debug and Display traits to enable 297 00:09:59,119 --> 00:09:59,460 printing. 298 00:10:00,460 --> 00:10:02,200 They should implement the From trait 299 00:10:02,400 --> 00:10:04,239 for any other error type that needs 300 00:10:04,439 --> 00:10:06,310 to be changed into the custom error type 301 00:10:07,000 --> 00:10:09,009 so that the question mark operator will take 302 00:10:09,209 --> 00:10:10,050 care of the conversion. 303 00:10:11,010 --> 00:10:13,110 Finally, Result type aliases 304 00:10:13,310 --> 00:10:15,420 are a common way to reduce the repetition 305 00:10:15,750 --> 00:10:17,550 of using the same custom error type 306 00:10:17,750 --> 00:10:18,600 everywhere in a crate. 307 00:10:20,120 --> 00:10:22,070 Next, let's look at a few crates 308 00:10:22,270 --> 00:10:24,080 that can help when our error types become 309 00:10:24,280 --> 00:10:25,150 even more complex.