1
00:00:00,040 --> 00:00:05,560
Welcome to module 7 on error handling crates. In this module,
2
00:00:05,630 --> 00:00:11,220
we're going to look at three error handling crates that can help as your custom error types become more complex.
3
00:00:12,140 --> 00:00:17,850
We're going to use the document service example from the previous module to demonstrate each crate's features.
4
00:00:18,740 --> 00:00:20,700
The crates we're going to cover are quick-error,
5
00:00:21,380 --> 00:00:21,599
error-chain,
6
00:00:21,680 --> 00:00:23,120
and failure.
7
00:00:24,540 --> 00:00:27,450
Let's start by reviewing the document service example.
8
00:00:28,840 --> 00:00:35,040
We have a DocumentServiceError enum with RateLimitExceeded and Io variants that derives Debug.
9
00:00:35,940 --> 00:00:41,150
We implemented the Error trait and the Display trait on the Error enum.
10
00:00:42,140 --> 00:00:49,050
We also added an implementation of the From trait to convert an io::Error to an instance of DocumentServiceError.
11
00:00:50,440 --> 00:00:54,190
To better illustrate how the error handling libraries affect the code,
12
00:00:54,640 --> 00:01:00,050
we're going to add a function called create_project that calls the create_document function multiple times.
13
00:01:01,540 --> 00:01:02,170
Then,
14
00:01:02,180 --> 00:01:04,469
to see the effects of the libraries on the code's
15
00:01:04,470 --> 00:01:11,750
behavior, we'll call the create_project function from main to represent an application that uses the document service library.
16
00:01:13,140 --> 00:01:14,690
The first time you run this code,
17
00:01:15,110 --> 00:01:17,530
it will print Project created successfully,
18
00:01:17,800 --> 00:01:20,420
and there will be four new files in your current directory.
19
00:01:21,340 --> 00:01:24,730
If you run this code again, without deleting the project files,
20
00:01:24,970 --> 00:01:28,250
the program will exit with an error that says File exists.
21
00:01:29,190 --> 00:01:34,350
Note that the message doesn't say which file exists or which create_document call failed,
22
00:01:34,740 --> 00:01:37,820
which would make it difficult to figure out why this error was happening.
23
00:01:39,180 --> 00:01:43,350
This is the starting point we're going to come back to before demonstrating each crate.
24
00:01:44,740 --> 00:01:48,020
So, let's take a look at what the quick-error crate does for us.
25
00:01:49,440 --> 00:01:49,930
First,
26
00:01:49,940 --> 00:01:52,119
let's add quick-error to Cargo.toml
27
00:01:52,120 --> 00:01:56,330
version 1.2.2. To follow along, for each crate,
28
00:01:56,630 --> 00:02:02,440
specify that the version should match exactly by adding the equals sign within the quotes.
29
00:02:02,700 --> 00:02:03,950
If you want, when you're done with this video,
30
00:02:04,340 --> 00:02:07,950
see if there's newer versions of each crate and explore how they're different.
31
00:02:09,340 --> 00:02:15,090
The quick-error crate provides its functionality with a macro.
32
00:02:15,170 --> 00:02:18,590
In main.rs, we bring the quick-error crate into scope along with its macros,
33
00:02:18,920 --> 00:02:21,050
which is what the macro_use annotation means.
34
00:02:21,940 --> 00:02:22,469
Next,
35
00:02:22,470 --> 00:02:24,879
we call the quick_error! macro around the Error
36
00:02:24,880 --> 00:02:25,350
enum.
37
00:02:26,340 --> 00:02:32,839
The macro requires curly brackets after each variant instead of a comma and naming the data inside the Io
38
00:02:32,840 --> 00:02:33,420
variant.
39
00:02:34,840 --> 00:02:40,350
The macro implements the Error trait, so we can remove that implementation and the use statement.
40
00:02:41,740 --> 00:02:42,440
Next,
41
00:02:42,540 --> 00:02:46,650
the quick_error! macro has a less verbose way to implement the Display trait.
42
00:02:48,040 --> 00:02:52,850
We can add just the text to each variant by calling what looks like a function named display.
43
00:02:53,790 --> 00:02:58,550
Then, we can remove the Display implementation and the use standard format statement.
44
00:03:00,140 --> 00:03:11,050
The quick_error! macro can also implement From traits. We can call what looks like a from function inside the curly braces within the Io variant definition.
45
00:03:12,540 --> 00:03:15,000
This behaves the same as the original code,
46
00:03:15,170 --> 00:03:16,750
but in a lot fewer lines.
47
00:03:17,640 --> 00:03:20,009
Adding more error variants would be less work
48
00:03:20,010 --> 00:03:20,350
too.
49
00:03:21,840 --> 00:03:22,420
Next,
50
00:03:22,640 --> 00:03:24,250
let's use a feature of quick-error!,
51
00:03:24,740 --> 00:03:29,850
adding the filename as context for the io::Error, so we can see which file already exists.
52
00:03:31,240 --> 00:03:40,050
We bring the quick_error::ResultExt trait into scope, add a file named String to the Io variant, and use the filename in the display for that variant.
53
00:03:41,440 --> 00:03:50,330
Then, replace the from call with a call to context that takes a string slice for the filename and an io::Error.
54
00:03:50,500 --> 00:04:04,050
Using closure-like syntax, we return a tuple of the cause and the filename converted to a string to create this variant. Finally, in create_document, we call context on the result and pass the filename. The io::Error gets past implicitly.
55
00:04:05,540 --> 00:04:08,390
Now we see the name of the file that already exists.
56
00:04:08,910 --> 00:04:11,050
This will let us figure out the problem faster.
57
00:04:12,440 --> 00:04:13,120
Next,
58
00:04:13,340 --> 00:04:19,689
let's look at what the error-chain crate provides. Starting the example back before we added the quick-error
59
00:04:19,690 --> 00:04:20,050
crate,
60
00:04:21,020 --> 00:04:31,250
let's add error-chain version 0.12.0 to Cargo.toml. error-chain also provides its functionality from a macro.
61
00:04:32,740 --> 00:04:38,050
So, again, in main.rs, we bring the crate into our project with the macro_use annotation.
62
00:04:39,520 --> 00:04:43,650
The error-chain crate recommends using the macro within an errors module.
63
00:04:45,140 --> 00:04:47,600
We'll also add a use statement with a glob,
64
00:04:47,810 --> 00:04:53,850
bringing everything in the errors module into the root module scope. For the rate limited error,
65
00:04:53,920 --> 00:04:56,490
we define it within a block called errors
66
00:04:56,530 --> 00:04:57,650
within the macro call.
67
00:04:59,090 --> 00:05:02,650
error_chain! also provides a concise way to implement Display.
68
00:05:04,040 --> 00:05:05,010
Coincidentally,
69
00:05:05,170 --> 00:05:06,780
it's the same as with quick-error!:
70
00:05:07,080 --> 00:05:13,720
specify the text in a function-like construct called display. error-chain! can implement the From trait
71
00:05:13,730 --> 00:05:14,150
too.
72
00:05:15,540 --> 00:05:17,630
Replace the block called foreign_links,
73
00:05:17,810 --> 00:05:19,650
then define the errors with what they wrap.
74
00:05:20,540 --> 00:05:22,340
Because of the way the macro works,
75
00:05:22,630 --> 00:05:26,339
we have to specify the full path, starting from the root module to the
76
00:05:26,340 --> 00:05:33,819
io::Error. error-chain will automatically use the Display implementation for the inner error.
77
00:05:33,820 --> 00:05:38,460
The error_chain! macro provides an implementation of the Error trait on the type it defines.
78
00:05:38,670 --> 00:05:41,550
So, at this point, we can remove all this code.
79
00:05:42,990 --> 00:05:47,650
The error_chain! macro defines a particular type structure that it thinks works best.
80
00:05:49,240 --> 00:05:53,100
There's a struct named Error that holds a variant of an enum named ErrorKind,
81
00:05:53,540 --> 00:06:01,050
which is what the types we specify in the macro become. To use these types then, we need to update create_document.
82
00:06:02,040 --> 00:06:07,899
error-chain! provides another macro named bail! that takes an ErrorKind and returns early with that kind in an error.
83
00:06:07,900 --> 00:06:10,469
The error_chain!
84
00:06:10,470 --> 00:06:15,550
macro also creates a Result type alias that uses the error it defines.
85
00:06:17,000 --> 00:06:21,150
That means we can remove the one we defined and the associated use statement.
86
00:06:22,590 --> 00:06:24,930
And now we're back to the same behavior as before,
87
00:06:24,940 --> 00:06:32,960
adding error-chain!, with less boilerplate, in a different way from quick-error!. Now for error-chain's extra features.
88
00:06:33,740 --> 00:06:38,040
error-chain! lets you add context errors by chaining errors into a list -
89
00:06:38,290 --> 00:06:39,410
hence the crate's name.
90
00:06:40,840 --> 00:06:41,720
For example,
91
00:06:41,940 --> 00:06:42,119
to
92
00:06:42,120 --> 00:06:44,050
add the filename to the io::Error,
93
00:06:44,440 --> 00:06:49,650
we can call the chain_err method on the result and provide an error message containing the filename.
94
00:06:51,090 --> 00:06:53,289
Now, cargo run shows the error message
95
00:06:53,290 --> 00:06:55,220
we just added that contains the filename.
96
00:06:56,740 --> 00:07:02,150
We can change the code in main that prints error messages to also print the chain of errors if there is one.
97
00:07:03,140 --> 00:07:08,610
We skip the first error in the chain because that's the one we've already printed. Now,
98
00:07:08,620 --> 00:07:09,319
cargo run
99
00:07:09,320 --> 00:07:17,750
prints the full chain of errors. error-chain! can also provide more information through a backtrace, like what you would get with a panic!.
100
00:07:19,140 --> 00:07:19,810
In main,
101
00:07:19,900 --> 00:07:21,550
we can print the backtrace as well.
102
00:07:22,470 --> 00:07:26,850
The backtrace method returns an Option because the error might not have a backtrace.
103
00:07:28,740 --> 00:07:38,550
Just using cargo run won't display the backtrace. Backtraces are only captured when the environment variable, RUST_BACKTRACE, is set to anything other than zero.
104
00:07:39,040 --> 00:07:43,650
Here, we've set the environment variable and can see where in our code the error came through.
105
00:07:44,340 --> 00:07:47,350
This can really help track down why errors are happening.
106
00:07:48,910 --> 00:07:51,450
The last crate we'll look at is the failure crate.
107
00:07:52,840 --> 00:08:09,399
Let's put failure version 0.1.5 and failure_derive version 0.1.5 in Cargo.toml. failure provides a procedural macro that lets you derive an implementation for the Fail trait.
108
00:08:09,400 --> 00:08:09,980
In main.rs,
109
00:08:10,160 --> 00:08:13,739
we need to bring the two crates into scope with macro_use on failure_derive.
110
00:08:15,580 --> 00:08:18,089
Then, we can derive the Fail trait on the Error
111
00:08:18,090 --> 00:08:18,450
enum.
112
00:08:19,940 --> 00:08:29,250
The failure crate implements the standard Error trait for everything that implements the Fail trait, so we can remove the implementation of the Error trait and the associated use statement.
113
00:08:30,680 --> 00:08:31,179
The failure_derive
114
00:08:31,180 --> 00:08:31,980
crate,
115
00:08:31,990 --> 00:08:33,550
also helps implement Display.
116
00:08:35,040 --> 00:08:45,250
We add the fail attribute with the argument named display set to the string to show. We can also refer to data within the variants using an underscore and the field index.
117
00:08:46,140 --> 00:08:47,040
And at this point,
118
00:08:47,150 --> 00:08:50,650
we no longer need the Display implementation or the associated use statement.
119
00:08:52,140 --> 00:08:58,400
failure also includes backtrace support. To add backtraces to all of our error variants,
120
00:08:58,640 --> 00:09:02,050
we need to bring failure, Backtrace, and Fail into our project's scope.
121
00:09:03,040 --> 00:09:07,540
Then, we store a backtrace in each variant and create a new Backtrace instance
122
00:09:07,600 --> 00:09:12,030
whenever we create a new error instance. In main,
123
00:09:12,040 --> 00:09:13,380
we print the backtrace
124
00:09:13,500 --> 00:09:18,929
if this error has a backtrace, and if a backtrace was generated. failure's
125
00:09:18,930 --> 00:09:21,950
backtrace also won't display if we use cargo run.
126
00:09:22,840 --> 00:09:24,410
When we set RUST_BACKTRACE,
127
00:09:24,450 --> 00:09:27,950
we get useful information about where in our code the error came from.
128
00:09:29,440 --> 00:09:32,350
So, which of these error crates should you use in your project?
129
00:09:32,740 --> 00:09:37,950
It depends - mostly on which features you need and how you'd like to work with the crates.
130
00:09:39,340 --> 00:09:43,450
Here's a handy table showing the unique aspects of each crate to consider.
131
00:09:44,010 --> 00:09:46,050
You should try them out in your own projects.
132
00:09:47,540 --> 00:09:48,530
In this module,
133
00:09:48,700 --> 00:09:53,179
we covered three error handling crates and looked at how to use them in our document service
134
00:09:53,180 --> 00:09:53,890
example
135
00:09:54,220 --> 00:09:57,350
to eliminate boilerplate and provide extra features.
136
00:09:58,340 --> 00:10:05,950
We saw how the quick-error crate provides a macro that helps to implement the display in From traits and provides a way to add context.
137
00:10:06,840 --> 00:10:16,269
The error-chain crate has the macro that defines error type structures that let you chain errors together and that have backtrace support. The failure and failure_derive
138
00:10:16,270 --> 00:10:22,750
crates provide derive macros to make implementing the traits easier and also provide backtrace support.
139
00:10:23,640 --> 00:10:24,290
Next,
140
00:10:24,580 --> 00:10:27,100
let's look at useful methods on Result and Option.