|
| 1 | +# Unrecoverable errors with panic! |
| 2 | + |
| 3 | +You've already seen the way to signal an unrecoverable error: the `panic!` |
| 4 | +macro. Here's an example of using `panic!`: |
| 5 | + |
| 6 | +```rust |
| 7 | +fn check_guess(number: u32) -> bool { |
| 8 | + if number > 100 { |
| 9 | + panic!("Guess was too big: {}", number); |
| 10 | + } |
| 11 | + |
| 12 | + number == 34 |
| 13 | +} |
| 14 | +``` |
| 15 | + |
| 16 | +This function accepts a guess between zero and a hundred, and checks if it's |
| 17 | +equivalent to the correct number, which is `34` in this case. It's kind of a |
| 18 | +silly function, but we need an example, so it works. |
| 19 | + |
| 20 | +There's no number type for "between zero and a hundred" in Rust, so we are |
| 21 | +accepting a `u32`, and then checking in the function's body to make sure the |
| 22 | +guess is in-bounds. If the number is too big, there's been an error: someone |
| 23 | +made a mistake. We then invoke `panic!` to say "something went wrong, we cannot |
| 24 | +continue to run this program." |
| 25 | + |
| 26 | +Checking some sort of condition and then `panic!`ing if something is wrong is a |
| 27 | +common use of `panic!`. In fact, there's a second macro in Rust, `assert!` for |
| 28 | +this case. `assert!` will check some kind of condition, and `panic!` if the |
| 29 | +condition is false. We could also write our function like this: |
| 30 | + |
| 31 | +```rust |
| 32 | +fn check_guess(number: u32) -> bool { |
| 33 | + assert!(number < 100); |
| 34 | + |
| 35 | + number == 34 |
| 36 | +} |
| 37 | +``` |
| 38 | + |
| 39 | +If we try and use `check_guess` in a program, and make an error: |
| 40 | + |
| 41 | +```rust,should_panic |
| 42 | +fn check_guess(number: u32) -> bool { |
| 43 | + assert!(number < 100); |
| 44 | +
|
| 45 | + number == 34 |
| 46 | +} |
| 47 | +
|
| 48 | +fn main() { |
| 49 | + let answer = check_guess(5); |
| 50 | + println!("answer was: {}", answer); |
| 51 | +
|
| 52 | + let answer = check_guess(34); |
| 53 | + println!("answer was: {}", answer); |
| 54 | +
|
| 55 | + let answer = check_guess(500); |
| 56 | + println!("answer was: {}", answer); |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +We'll see output like this: |
| 61 | + |
| 62 | +```text |
| 63 | +answer was: false |
| 64 | +answer was: true |
| 65 | +
|
| 66 | +thread '<main>' panicked at 'assertion failed: number < 100', <anon>:2 |
| 67 | +``` |
| 68 | + |
| 69 | +First, `5` was okay, but false. Then, `34` was okay, but true. Finally, `500` |
| 70 | +caused a panic. |
| 71 | + |
| 72 | +Panics cause your program to stop executing. To check this, we could move the |
| 73 | +failing case above the good cases: |
| 74 | + |
| 75 | +```rust,should_panic |
| 76 | +# fn check_guess(number: u32) -> bool { |
| 77 | +# assert!(number < 100); |
| 78 | +# |
| 79 | +# number == 34 |
| 80 | +# } |
| 81 | +# |
| 82 | +fn main() { |
| 83 | + let answer = check_guess(500); |
| 84 | + println!("answer was: {}", answer); |
| 85 | +
|
| 86 | + let answer = check_guess(5); |
| 87 | + println!("answer was: {}", answer); |
| 88 | +
|
| 89 | + let answer = check_guess(34); |
| 90 | + println!("answer was: {}", answer); |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +If we run it, we'll see that we never check `5` or `34`: |
| 95 | + |
| 96 | +```text |
| 97 | +thread '<main>' panicked at 'assertion failed: number < 100', <anon>:2 |
| 98 | +``` |
| 99 | + |
| 100 | +This is why we call `panic!` an unrecoverable error: the other code never gets a |
| 101 | +chance to run. Our program just ends. |
| 102 | + |
| 103 | +But what does it mean to "end a program"? As it turns out, there are multiple |
| 104 | +strategies for processing an unrecoverable error. The two main ones are |
| 105 | +'unwinding' and 'aborting'. |
| 106 | + |
| 107 | +## Unwinding |
| 108 | + |
| 109 | +By default, when a `panic!` happens in Rust, it starts doing something called |
| 110 | +"unwinding". To explain unwinding, let's consider a slightly more complex |
| 111 | +program: |
| 112 | + |
| 113 | +```rust,should_panic |
| 114 | +fn step1() { |
| 115 | + let s = String::from("Step 1"); |
| 116 | + step2(); |
| 117 | +} |
| 118 | +
|
| 119 | +fn step2() { |
| 120 | + let s = String::from("Step 2"); |
| 121 | + step3(); |
| 122 | +} |
| 123 | +
|
| 124 | +fn step3() { |
| 125 | + let s = String::from("Step 3"); |
| 126 | + check_guess(500); |
| 127 | +} |
| 128 | +
|
| 129 | +fn check_guess(number: u32) -> bool { |
| 130 | + assert!(number < 100); |
| 131 | +
|
| 132 | + number == 34 |
| 133 | +} |
| 134 | +
|
| 135 | +fn main() { |
| 136 | + step1(); |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +Here, we have four functions total: `step1` calls `step2` which calls `step3` |
| 141 | +which then calls `check_guess`. Something like this diagram, with each of the |
| 142 | +variable bindings written in: |
| 143 | + |
| 144 | +```text |
| 145 | +> main |
| 146 | + | |
| 147 | + > step1 String: "Step 1" |
| 148 | + | |
| 149 | + > step2 String: "Step 2" |
| 150 | + | |
| 151 | + > step3 String: "Step 3" |
| 152 | + | |
| 153 | + > check_guess: u32: 500 |
| 154 | +``` |
| 155 | + |
| 156 | +When `check_guess` causes a `panic!` via `assert!`, it will walk back through |
| 157 | +each of these functions, and clean them up. We haven't yet talked about |
| 158 | +destructors in Rust, that'll come in Chapter XX. For now, think about it this |
| 159 | +way: simple values like that `u32` can be destroyed by freeing their memory. |
| 160 | +More complicated values, like `String`s, have more complicated needs. In these |
| 161 | +cases, there is a function that does this cleanup. We call this a "drop |
| 162 | +function" in Rust. |
| 163 | + |
| 164 | +So, the first thing that will happen is that `check_guess`, our current |
| 165 | +function, gets cleaned up. There's only one value, the `500`, and so its memory |
| 166 | +will be freed. Now our program looks like this: |
| 167 | + |
| 168 | +```text |
| 169 | +> main |
| 170 | + | |
| 171 | + > step1 String: "Step 1" |
| 172 | + | |
| 173 | + > step2 String: "Step 2" |
| 174 | + | |
| 175 | + > step3 String: "Step 3" |
| 176 | +``` |
| 177 | + |
| 178 | +Now we're on `step3`. In the same way, Rust will call the `String`'s drop |
| 179 | +function, deallocating the `String`. Once that's done, we can move on: |
| 180 | + |
| 181 | +```text |
| 182 | +> main |
| 183 | + | |
| 184 | + > step1 String: "Step 1" |
| 185 | + | |
| 186 | + > step2 String: "Step 2" |
| 187 | +``` |
| 188 | + |
| 189 | +The pattern continues: `step2` has a single value, and `"Step 2"` has its |
| 190 | +drop function called. Next! |
| 191 | + |
| 192 | +```text |
| 193 | +> main |
| 194 | + | |
| 195 | + > step1 String: "Step 1" |
| 196 | +``` |
| 197 | + |
| 198 | +Almost done! `step1` also has a `String`, so Rust will invoke its drop function. |
| 199 | + |
| 200 | +```text |
| 201 | +> main |
| 202 | +``` |
| 203 | + |
| 204 | +Finally, all we have left is `main()`. It has no variable bindings or arguments |
| 205 | +in this case, so there's nothing to clean up. Our whole program has been delt |
| 206 | +with, and so terminates execution, after printing a message. |
| 207 | + |
| 208 | +## Aborting |
| 209 | + |
| 210 | +Doing all that is a lot of work! And the end result is that our program |
| 211 | +terminates. Handing panics with unwinding is useful in many scenarios, but some |
| 212 | +applications would rather skip straight to the end, and 'abort'. With some |
| 213 | +configuration, Cargo will allow us to use this alternate implementation of |
| 214 | +`panic!`. What happens when that's enabled? Let's consider what our call stack |
| 215 | +looked like: |
| 216 | + |
| 217 | +```text |
| 218 | +> main |
| 219 | + | |
| 220 | + > step1 String: "Step 1" |
| 221 | + | |
| 222 | + > step2 String: "Step 2" |
| 223 | + | |
| 224 | + > step3 String: "Step 3" |
| 225 | + | |
| 226 | + > check_guess: u32: 500 |
| 227 | +``` |
| 228 | + |
| 229 | +With an abort implementation of `panic!`, instead of walking back up through the |
| 230 | +previous functions and cleaning everything up, we skip straight to the end: our |
| 231 | +program terminates. But what about our resources? In the case of memory, like is |
| 232 | +the case with `String`s, the operating system will reclaim the memory. So for |
| 233 | +this program, the two cases are identical, but aborting is much more efficient. |
| 234 | +Other, more complex resources work differently, however, and so aborting may not |
| 235 | +always be the right choice either. It depends! |
0 commit comments