|
| 1 | +## Unrecoverable Errors with `panic!` |
| 2 | + |
| 3 | +Sometimes, bad things happen, and there's nothing that you can do about it. For |
| 4 | +these cases, Rust has a macro, `panic!`. When this macro executes, your program |
| 5 | +will print a failure message, unwind and clean up the stack, and then quit. The |
| 6 | +most common reason for this is when a bug of some kind has been detected, and |
| 7 | +it's not clear how to handle the error. |
| 8 | + |
| 9 | +<!-- PROD: START BOX --> |
| 10 | + |
| 11 | +> #### Unwinding |
| 12 | +> By default, when a `panic!` happens in Rust, the program starts |
| 13 | +> *unwinding*, which means Rust walks back up the stack and cleans up the data |
| 14 | +> from each function it encounters. Doing that walking and cleanup is a lot of |
| 15 | +> work. The alternative is to immediately `abort`, which ends the program |
| 16 | +> without cleaning up. Memory that the program was using will need to be cleaned |
| 17 | +> up by the operating system. If you're in a situation where you need to make |
| 18 | +> the resulting binary as small as possible, you can switch from unwinding on |
| 19 | +> panic to aborting on panic by adding `panic = 'abort'` to the appropriate |
| 20 | +> `[profile]` sections in your `Cargo.toml`. |
| 21 | +
|
| 22 | +<!-- PROD: END BOX --> |
| 23 | + |
| 24 | +Let's try out calling `panic!()` with a simple program: |
| 25 | + |
| 26 | +```rust,should_panic |
| 27 | +fn main() { |
| 28 | + panic!("crash and burn"); |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +If you run it, you'll see something like this: |
| 33 | + |
| 34 | +```bash |
| 35 | +$ cargo run |
| 36 | + Compiling panic v0.1.0 (file:///projects/panic) |
| 37 | + Finished debug [unoptimized + debuginfo] target(s) in 0.25 secs |
| 38 | + Running `target/debug/panic` |
| 39 | +thread 'main' panicked at 'crash and burn', src/main.rs:2 |
| 40 | +note: Run with `RUST_BACKTRACE=1` for a backtrace. |
| 41 | +error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) |
| 42 | +``` |
| 43 | +
|
| 44 | +There are three lines of error message here. The first line shows our panic |
| 45 | +message and the place in our source code where the panic occurred: |
| 46 | +`src/main.rs`, line two. |
| 47 | +
|
| 48 | +But that only shows us the exact line that called `panic!`. That's not always |
| 49 | +useful. Let's look at another example to see what it's like when a `panic!` |
| 50 | +call comes from code we call instead of from our code directly: |
| 51 | + |
| 52 | +```rust,should_panic |
| 53 | +fn main() { |
| 54 | + let v = vec![1, 2, 3]; |
| 55 | +
|
| 56 | + v[100]; |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +We're attempting to access the hundredth element of our vector, but it only has |
| 61 | +three elements. In this situation, Rust will panic. Using `[]` is supposed to |
| 62 | +return an element. If you pass `[]` an invalid index, though, there's no |
| 63 | +element that Rust could return here that would be correct. |
| 64 | + |
| 65 | +Other languages like C will attempt to give you exactly what you asked for in |
| 66 | +this situation, even though it isn't what you want: you'll get whatever is at |
| 67 | +the location in memory that would correspond to that element in the vector, |
| 68 | +even though the memory doesn't belong to the vector. This is called a *buffer |
| 69 | +overread*, and can lead to security vulnerabilities if an attacker can |
| 70 | +manipulate the index in such a way as to read data they shouldn't be allowed to |
| 71 | +that is stored after the array. |
| 72 | + |
| 73 | +In order to protect your program from this sort of vulnerability, if you try to |
| 74 | +read an element at an index that doesn't exist, Rust will stop execution and |
| 75 | +refuse to continue with an invalid value. Let's try it and see: |
| 76 | + |
| 77 | +```bash |
| 78 | +$ cargo run |
| 79 | + Compiling panic v0.1.0 (file:///projects/panic) |
| 80 | + Finished debug [unoptimized + debuginfo] target(s) in 0.27 secs |
| 81 | + Running `target/debug/panic` |
| 82 | +thread 'main' panicked at 'index out of bounds: the len is 3 but the index is |
| 83 | +100', ../src/libcollections/vec.rs:1265 |
| 84 | +note: Run with `RUST_BACKTRACE=1` for a backtrace. |
| 85 | +error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) |
| 86 | +``` |
| 87 | +
|
| 88 | +This points at a file we didn't write, `../src/libcollections/vec.rs`. That's |
| 89 | +the implementation of `Vec<T>` in the standard library. While it's easy to see |
| 90 | +in this short program where the error was, it would be nicer if we could have |
| 91 | +Rust tell us what line in our program caused the error. |
| 92 | +
|
| 93 | +That's what the next line, the `note` is about. If we set the `RUST_BACKTRACE` |
| 94 | +environment variable, we'll get a backtrace of exactly how the error happend. |
| 95 | +Let's try that. Listing 9-1 shows the output: |
| 96 | +
|
| 97 | +```bash |
| 98 | +$ RUST_BACKTRACE=1 cargo run |
| 99 | + Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs |
| 100 | + Running `target/debug/panic` |
| 101 | +thread 'main' panicked at 'index out of bounds: the len is 3 but the index is |
| 102 | +100', ../src/libcollections/vec.rs:1265 |
| 103 | +stack backtrace: |
| 104 | + 1: 0x560956150ae9 - |
| 105 | +std::sys::backtrace::tracing::imp::write::h482d45d91246faa2 |
| 106 | + 2: 0x56095615345c - |
| 107 | +std::panicking::default_hook::_{{closure}}::h89158f66286b674e |
| 108 | + 3: 0x56095615291e - std::panicking::default_hook::h9e30d428ee3b0c43 |
| 109 | + 4: 0x560956152f88 - |
| 110 | +std::panicking::rust_panic_with_hook::h2224f33fb7bf2f4c |
| 111 | + 5: 0x560956152e22 - std::panicking::begin_panic::hcb11a4dc6d779ae5 |
| 112 | + 6: 0x560956152d50 - std::panicking::begin_panic_fmt::h310416c62f3935b3 |
| 113 | + 7: 0x560956152cd1 - rust_begin_unwind |
| 114 | + 8: 0x560956188a2f - core::panicking::panic_fmt::hc5789f4e80194729 |
| 115 | + 9: 0x5609561889d3 - |
| 116 | +core::panicking::panic_bounds_check::hb2d969c3cc11ed08 |
| 117 | + 10: 0x56095614c075 - _<collections..vec..Vec<T> as |
| 118 | +core..ops..Index<usize>>::index::hb9f10d3dadbe8101 |
| 119 | + at ../src/libcollections/vec.rs:1265 |
| 120 | + 11: 0x56095614c134 - panic::main::h2d7d3751fb8705e2 |
| 121 | + at /projects/panic/src/main.rs:4 |
| 122 | + 12: 0x56095615af46 - __rust_maybe_catch_panic |
| 123 | + 13: 0x560956152082 - std::rt::lang_start::h352a66f5026f54bd |
| 124 | + 14: 0x56095614c1b3 - main |
| 125 | + 15: 0x7f75b88ed72f - __libc_start_main |
| 126 | + 16: 0x56095614b3c8 - _start |
| 127 | + 17: 0x0 - <unknown> |
| 128 | +error: Process didn't exit successfully: `target/debug/panic` (exit code: 101) |
| 129 | +``` |
| 130 | +
|
| 131 | +<caption> |
| 132 | +Listing 9-1: The backtrace generated by a call to `panic!` displayed when |
| 133 | +the environment variable `RUST_BACKTRACE` is set |
| 134 | +</caption> |
| 135 | +
|
| 136 | +That's a lot of output! Line 11 of the backtrace points to the line in our |
| 137 | +project causing the problem: `src/main.rs` line four. The key to reading the |
| 138 | +backtrace is to start from the top and read until we see files that we wrote: |
| 139 | +that's where the problem originated. If we didn't want our program to panic |
| 140 | +here, this line is where we would start investigating in order to figure out |
| 141 | +how we got to this location with values that caused the panic. |
| 142 | +
|
| 143 | +Now that we've covered how to `panic!` to stop our code's execution and how to |
| 144 | +debug a `panic!`, let's look at how to instead return and use recoverable |
| 145 | +errors with `Result`. |
0 commit comments