83216ad909561630ab225bf0ec70f3e797ecec73
[web.git] / personal / _posts / 2019-11-25-how-to-panic-in-rust.md
1 ---
2 title: "How to Panic in Rust"
3 categories: rust
4 ---
5
6 What exactly happens when you write `panic!()`?
7 I recently spent a lot of time looking at the parts of the standard library concerned with this, and it turns out the answer is quite complicated!
8 I have not been able to find docs explaining the high-level picture of panicking in Rust, so this feels worth writing down.
9
10 <!-- MORE -->
11
12 (Shameless plug: the reason I looked at this is that @Aaron1011 implemented unwinding support in Miri.
13 I wanted to see that in Miri since forever and never had the time to implement it myself, so it was really great to see someone just submit PRs for that out of the blue.
14 After a lot of review rounds, this landed just recently.
15 There [are still some rough edges](https://github.com/rust-lang/miri/issues?q=is%3Aissue+is%3Aopen+label%3AA-panics), but the foundations are solid.)
16
17 The purpose of this post is to document the high-level structure and the relevant interfaces that come into play on the Rust side of this.
18 The actual mechanism of unwinding is a totally different matter (and one that I am not qualified to speak about).
19
20 ## High-level structure
21
22 When trying to figure out how panicking works by reading the code in libstd, one can easily get lost in the maze.
23 There are multiple layers of indirection that are only put together by the linker,
24 there is the [`#[panic_handler]` attribute](https://doc.rust-lang.org/nomicon/panic-handler.html) and the ["panic runtime"](https://github.com/rust-lang/rfcs/blob/master/text/1513-less-unwinding.md) (controlled by `-C panic`) and ["panic hooks"](https://doc.rust-lang.org/std/panic/fn.set_hook.html),
25 and it turns out panicking in `#[no_std]` context takes an entirely different code path... there is just a lot going on.
26 To make things worse, [the RFC describing panic hooks](https://github.com/rust-lang/rfcs/blob/master/text/1328-global-panic-handler.md) calls them "panic handler", but that term has since been re-purposed.
27
28 I think the best place to start are the interfaces controlling the two indirections:
29
30 * The *panic runtime* is used by libstd to control what happens after the panic information has been printed to stderr: either we abort (`-C panic=abort`) or we unwind (`-C panic=unwind`).
31   (The panic runtime also provides the implementation for [`catch_unwind`](https://doc.rust-lang.org/std/panic/fn.catch_unwind.html) but we are not concerned with that here.)
32
33 * The *panic handler* is used by libcore to implement (a) panics inserted by codegen (such as panics caused by arithmetic overflow or out-of-bounds array/slice indexing) and (b) the `core::panic!` macro (this is the `panic!` macro in libcore itself and in `#[no_std]` context in general).
34
35 Both of these interfaces are implemented through `extern` blocks: listd/libcore, respectively, just import some function that they delegate to, and somewhere entirely else in the crate tree, that function gets implemented.
36 The import only gets resolved during linking; looking locally at that code, one cannot tell where the actual implementation of the respective interface lives.
37 No wonder that I got lost several times along the way.
38
39 In the following, both of these interfaces will come up a lot; when you get confused, the first thing to check is if you just mixed up panic *handler* and panic *runtime*.
40 (And remember there's also panic *hooks*, we will get to those.)
41 That happens to me all the time.
42
43 Moreover, `core::panic!` and `std::panic!` are *not* the same; as we will see, they take very different code paths.
44 libcore and libstd each implement their own way to cause panics:
45
46 * libcore's `core::panic!` does very little, it basically just delegates to the panic *handler* immediately.
47 * libstd's `std::panic!` (the "normal" `panic!` macro in Rust) triggers a fully-featured panic machinery that provides a user-controlled [panic *hook*](https://doc.rust-lang.org/std/panic/fn.set_hook.html).
48   The default hook will print the panic message to stderr.
49   After the hook is done, libstd delegates to the panic *runtime*.
50
51     libstd also provides a panic *handler* that calls the same machinery, so `core::panic!` also ends up here.
52
53
54 Let us now look at these pieces in a bit more detail.
55
56 ## Panic Runtime
57
58 The [interface to the panic runtime](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libstd/panicking.rs#L48) (introduced by [this RFC](https://github.com/rust-lang/rfcs/blob/master/text/1513-less-unwinding.md)) is a function `__rust_start_panic(payload: usize) -> u32` that gets imported by libstd, and that is later resolved by the linker.
59
60 The `usize` argument here actually is a `*mut &mut dyn core::panic::BoxMeUp` -- this is where the "payload" of the panic (the information available when it gets caught) gets passed in.
61 `BoxMeUp` is an unstable internal implementation detail, but [looking at the trait](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libcore/panic.rs#L268-L271) we can see that all it really does is wrap a `dyn Any + Send`, which is the [type of the panic payload](https://doc.rust-lang.org/std/thread/type.Result.html) as returned by `catch_unwind` and `thread::spawn`.
62 `BoxMeUp::box_me_up` returns a `Box<dyn Any + Send>`, but as a raw pointer (because `Box` is not available in the context where this trait is defined); `BoxMeUp::get` just borrows the contents.
63
64 The two implementations of this interface Rust ships with are [`libpanic_unwind`](https://github.com/rust-lang/rust/tree/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libpanic_unwind) for `-C panic=unwind` (the default on most platforms) and [`libpanic_abort`](https://github.com/rust-lang/rust/tree/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libpanic_abort) for `-C panic=abort`.
65
66 ## `std::panic!`
67
68 On top of the panic *runtime* interface, libstd implements the default Rust panic machinery in the internal [`std::panicking`](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libstd/panicking.rs) module.
69
70 #### `rust_panic_with_hook`
71
72 The key function that everything passes through is [`rust_panic_with_hook`](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libstd/panicking.rs#L435):
73 {% highlight rust %}
74 fn rust_panic_with_hook(
75     payload: &mut dyn BoxMeUp,
76     message: Option<&fmt::Arguments<'_>>,
77     file_line_col: &(&str, u32, u32),
78 ) -> !
79 {% endhighlight %}
80 This function takes a panic source location, an optional unformatted panic message (see the [`fmt::Arguments`](https://doc.rust-lang.org/std/fmt/struct.Arguments.html) docs), and a payload.
81
82 Its main job is to call whatever the current panic hook is.
83 Panic hooks have a [`PanicInfo`](https://doc.rust-lang.org/core/panic/struct.PanicInfo.html) argument, so we need a panic source location, format information for a panic message, and a payload.
84 This matches `rust_panic_with_hook`'s arguments quite nicely!
85 `file_line_col` and `message` can be used directly for the first two elements; `payload` gets turned into `&(dyn Any + Send)` through the `BoxMeUp` interface.
86
87 Interestingly, the *default* panic hook entirely ignores the `message`; what you actually see printed is [the `payload` downcast to `&str` or `String`](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libstd/panicking.rs#L171-L177) (whatever works).
88 Supposedly, the caller should ensure that formatting `message`, if present, gives the same result.
89 (And the ones we discuss below do ensure this.)
90
91 Finally, `rust_panic_with_hook` dispatches to the current panic *runtime*.
92 At this point, only the `payload` is still relevant -- and that is important: `message` (as the `'_` lifetime indicates) may contain short-lived references, but the panic payload will be propagated up the stack and hence must be `'static`.
93 The `'static` bound is quite well hidden there, but after a while I realized that [`Any`](https://doc.rust-lang.org/std/any/trait.Any.html) implies `'static` (and remember `dyn BoxMeUp` is just used to obtain a `Box<dyn Any + Send>`).
94
95 #### libstd panicking entry points
96
97 `rust_panic_with_hook` is a private function to `std::panicking`; the module provides three separate entry points on top of this central function:
98
99 * the [default panic handler implementation](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libstd/panicking.rs#L301), backing (as we will see) panics from `core::panic!` and built-in panics (from arithmetic overflow or array/slice indexing checks).
100   This obtains as input a [`PanicInfo` ](https://doc.rust-lang.org/core/panic/struct.PanicInfo.html), and it has to turn that into arguments for `rust_panic_with_hook`.
101   Curiously, even though the components of `PanicInfo` and the arguments of `rust_panic_with_hook` match up pretty well and seem like they could just be forwarded, that is *not* what happens.
102   Instead, libstd entirely *ignores* the `payload` component of the `PanicInfo`, and sets up the actual payload (passed to `rust_panic_with_hook`) such that it contains [the formatted `message`](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libstd/panicking.rs#L348).
103
104 * [`begin_panic_fmt`](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libstd/panicking.rs#L319), backing the [format string version of `std::panic!`](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libstd/macros.rs#L22-L23) (i.e., this is used when you pass multiple arguments to the macro).
105   This basically just packages the format string arguments into a `PanicInfo` (with a [dummy payload](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libcore/panic.rs#L56)) and calls the default panic handler that we just discussed.
106
107 * [`begin_panic`](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libstd/panicking.rs#L388), backing the [single-argument version of `std::panic!`](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libstd/macros.rs#L16).
108   Interestingly, this uses a very different code path than the other two entry points!
109   In particular, this is the only entry point that permits passing in an *arbitrary payload*.
110   That payload is just [converted into a `Box<dyn Any + Send>`](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libstd/panicking.rs#L415) so that it can be passed to `rust_panic_with_hook`, and that's it.
111   
112     In particular, a panic hook that looks at the `message` field of the `PanicData` it is passed will *not* be able to see the message in a `std::panic!("do panic")`, but it *will* see the message in a `std::panic!("panic with data: {}", data)` as the latter passes through `begin_panic_fmt` instead.
113   That seems quite surprising. (But also note that `PanicData::message()` is not stable yet.)
114
115 ## Panic Handler
116
117 All of the `std::panic!` machinery is really useful, but it relies on heap allocations through `Box` which is not always available.
118 To give libcore a way to cause panics, [panic handlers were introduced](https://github.com/rust-lang/rfcs/blob/master/text/2070-panic-implementation.md).
119 As we have seen, if libstd is available, it provides an implementation of that interface to wire `core::panic!` into the libstd panic machinery.
120
121 The [interface to the panic handler](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libcore/panicking.rs#L78) is a function `fn panic(info: &core::panic::PanicInfo) -> !` that libcore imports and that is later resolved by the linker.
122 The [`PanicInfo` ](https://doc.rust-lang.org/core/panic/struct.PanicInfo.html) type is the same as for panic hooks: it contains a panic source location, a panic message, and a payload (a `dyn Any + Send`).
123 The panic message is represented as [`fmt::Arguments`](https://doc.rust-lang.org/std/fmt/struct.Arguments.html), i.e., a format string with its arguments that has not been formatted yet.
124
125 ## `core::panic!`
126
127 On top of the panic handler interface, libcore provides a [minimal panic API](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libcore/panicking.rs).
128 The [`core::panic!`](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libcore/macros/mod.rs#L10-L26) macro creates a `fmt::Arguments` which is then [passed to the panic handler](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libcore/panicking.rs#L82).
129 No formatting happens here as that would require heap allocations; this is why `PanicInfo` contains an "uninterpreted" format string with its arguments.
130
131 Curiously, the `payload` field of the `PanicInfo` that gets passed to the panic handler is always set to [a dummy value](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libcore/panic.rs#L56).
132 This explains why the libstd panic handler ignores the payload (and instead constructs a new payload from the `message`), but that makes me wonder why that field is part of the panic handler API in the first place.
133 Another consequence of this is that `core::panic!("message")` and `std::panic!("message")` (the variants without any formatting) actually result in very different panics: the former gets turned into `fmt::Arguments`, passed through the panic handler interface, and then libstd creates a `String` payload by formatting it.
134 The latter, however, directly uses the `&str` as a payload, and the `message` field remains `None` (as already mentioned).
135
136 ## Conclusion
137
138 We have walked through 4 layers of APIs, 2 of which are indirected through imported function calls and resolved by the linker.
139 That's quite a journey!
140 But we have reached the end now.
141 I hope you [didn't panic](https://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy#Don't_Panic) yourself along the way. ;)
142
143 I mentioned some things as being surprising.
144 Turns out they all have to do with the fact that panic hooks and panic handlers share the `PanicInfo` struct in their interface, which contains *both* an optional not-yet-formatted `message` and a type-erased `payload`:
145 * The panic *hook* can always find the already formatted message in the `payload`, so the `message` seems pointless for hooks.
146   In fact, `message` can be missing even if `payload` contains a message (e.g., for `std::panic!("message")`).
147 * The panic *handler* will never actually receive a useful `payload`, so that field seems pointless for handlers.
148
149 Reading the [panic handler RFC](https://github.com/rust-lang/rfcs/blob/master/text/2070-panic-implementation.md), it seems like the plan was for `core::panic!` to also support arbitrary payloads, but so far that has not materialized.
150 However, even with that future extension, I think we have the invariant that when `message` is `Some`, then either `payload == &NoPayload` (so the payload is redundant) or `payload` is the formatted message (so the message is redundant).
151 I wonder if there is any case where *both* fields will be useful -- and if not, couldn't we encode that by making them two variants of an `enum`?
152 There are probably good reasons against that proposal and for the current design; it would be great to get them documented somewhere. :)
153
154 There is a lot more to say, but at this point, I invite you to follow the links to the source code that I included above.
155 With the high-level structure in mind, you should be able to follow that code.
156 If people think this overview would be worth putting somewhere more permanently, I'd be happy to work this blog post into some form of docs -- I am not sure what would be a good place for those, though.
157 And if you find any mistakes in what I wrote, please let me know!