also mention panic strategy
[web.git] / ralf / _posts / 2019-11-25-how-to-panic-in-rust.md
index 66ca0ab1d8a7e216b262583a2d1a68cefd842d28..0a9d2ba21b7bba82c4ba7cf740acde09360788a6 100644 (file)
@@ -1,9 +1,10 @@
 ---
 title: "How to Panic in Rust"
 categories: rust
+forum: https://internals.rust-lang.org/t/how-to-panic-in-rust/11368
 ---
 
-What exactly happens when you write `panic!()`?
+What exactly happens when you `panic!()`?
 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!
 I have not been able to find docs explaining the high-level picture of panicking in Rust, so this feels worth writing down.
 
@@ -21,16 +22,17 @@ The actual mechanism of unwinding is a totally different matter (and one that I
 
 When trying to figure out how panicking works by reading the code in libstd, one can easily get lost in the maze.
 There are multiple layers of indirection that are only put together by the linker,
-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),
+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 the panic *strategy*, which is set via `-C panic`) and ["panic hooks"](https://doc.rust-lang.org/std/panic/fn.set_hook.html),
 and it turns out panicking in `#[no_std]` context takes an entirely different code path... there is just a lot going on.
 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.
 
 I think the best place to start are the interfaces controlling the two indirections:
 
-* 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`).
+* The *panic runtime* is used by libstd to control what happens after the panic information has been printed to stderr.
+  It is determined by the panic *strategy*: either we abort (`-C panic=abort`) or we unwind (`-C panic=unwind`).
   (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.)
 
-* 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).
+* The *panic handler* is used by libcore to implement (a) panics inserted by code generation (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).
 
 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.
 The import only gets resolved during linking; looking locally at that code, one cannot tell where the actual implementation of the respective interface lives.
@@ -69,7 +71,7 @@ On top of the panic *runtime* interface, libstd implements the default Rust pani
 
 #### `rust_panic_with_hook`
 
-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):
+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-L437):
 {% highlight rust %}
 fn rust_panic_with_hook(
     payload: &mut dyn BoxMeUp,
@@ -96,11 +98,16 @@ The `'static` bound is quite well hidden there, but after a while I realized tha
 
 `rust_panic_with_hook` is a private function to `std::panicking`; the module provides three separate entry points on top of this central function:
 
-* 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).
+* 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 out-of-bounds array/slice indexing).
   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`.
   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.
   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).
 
+    In particular, this means that the panic *runtime* is irrelevant for `no_std` applications.
+  It only comes into play when libstd's panic handler implementation is used.
+  (The panic *strategy* selected via `-C panic` still has an effect as it also influences code generation.
+  For example, with `-C panic=abort` code can become simpler as it does not need to support unwinding.)
+
 * [`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).
   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.
 
@@ -130,9 +137,14 @@ No formatting happens here as that would require heap allocations; this is why `
 
 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).
 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.
-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.
+Another consequence of this is that [`core::panic!("message")`](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libcore/macros/mod.rs#L15) and [`std::panic!("message")`](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libstd/macros.rs#L16) (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.
 The latter, however, directly uses the `&str` as a payload, and the `message` field remains `None` (as already mentioned).
 
+Some elements of the libcore panic API are lang items because the compiler inserts calls to these functions during code generation:
+* The [`panic` lang item](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libcore/panicking.rs#L39) is called when the compiler needs to raise a panic that does not require any formatting (such as arithmetic overflow); this is the same function that also backs single-argument [`core::panic!`](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libcore/macros/mod.rs#L15).
+* The [`panic_bounds_check`](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libcore/panicking.rs#L55) lang item is called on a failed array/slice bounds check.
+  It calls into the same method as [`core::panic!` with formatting](https://github.com/rust-lang/rust/blob/7d761fe0462ba0f671a237d0bb35e3579b8ba0e8/src/libcore/macros/mod.rs#L21-L24).
+
 ## Conclusion
 
 We have walked through 4 layers of APIs, 2 of which are indirected through imported function calls and resolved by the linker.
@@ -151,7 +163,7 @@ However, even with that future extension, I think we have the invariant that whe
 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`?
 There are probably good reasons against that proposal and for the current design; it would be great to get them documented somewhere. :)
 
-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 and read the local comments.
-With the high-level structure in mind, those comments should be able to fill out any missing details.
-If people think this information 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.
+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.
+With the high-level structure in mind, you should be able to follow that code.
+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.
 And if you find any mistakes in what I wrote, please let me know!