diff --git a/text/2070-panic-implementation.md b/text/2070-panic-implementation.md new file mode 100644 index 00000000000..8be8e3ddc1b --- /dev/null +++ b/text/2070-panic-implementation.md @@ -0,0 +1,238 @@ +- Feature Name: panic_implementation +- Start Date: 2017-07-19 +- RFC PR: https://github.com/rust-lang/rfcs/pull/2070 +- Rust Issue: https://github.com/rust-lang/rust/issues/44489 + +# Summary +[summary]: #summary + +Provide a stable mechanism to specify the behavior of `panic!` in no-std +applications. + +# Motivation +[motivation]: #motivation + +The `#![no_std]` attribute was stabilized some time ago and it made possible to +build no-std libraries on stable. However, to this day no-std applications +still require a nightly compiler to be built. The main cause of this is that +the behavior of `panic!` is left undefined in no-std context, and the only way +to specify a panicking behavior is through the unstable `panic_fmt` [language +item]. + +[language item]: https://doc.rust-lang.org/unstable-book/language-features/lang-items.html + +This document proposes a stable mechanism to specify the behavior of `panic!` in +no-std context. This would be a step towards enabling development of no-std +applications like device firmware, kernels and operating systems on the stable +channel. + +# Detailed design +[design]: #detailed-design + +## Constraints + +`panic!` in no-std environments must continue to be free of memory allocations +and [its API] can only be changed in a backward compatible way. + +[its API]: https://doc.rust-lang.org/core/macro.panic.html + +Although not a hard constraint, the cognitive load of the mechanism would be +greatly reduced if it mimicked the existing [custom panic hook] mechanism as +much as possible. + +[custom panic hook]: https://doc.rust-lang.org/std/panic/fn.set_hook.html + +## `PanicInfo` + +The types [`std::panic::PanicInfo`] and [`std::panic::Location`] will be moved +into the `core` crate, and `PanicInfo` will gain a new method: + +[`std::panic::PanicInfo`]: https://doc.rust-lang.org/std/panic/struct.PanicInfo.html +[`std::panic::Location`]: https://doc.rust-lang.org/std/panic/struct.Location.html + +``` rust +impl PanicInfo { + pub fn message(&self) -> Option<&fmt::Arguments> { .. } +} +``` + +This method returns `Some` if the `panic!` invocation needs to do any formatting +like `panic!("{}: {}", key , value)` does. + +### `fmt::Display` + +For convenience, `PanicInfo` will gain an implementation of the `fmt::Display` +trait that produces a message very similar to the one that the standard `panic!` +hook produces. For instance, this program: + +``` rust +use std::panic::{self, PanicInfo}; + +fn panic_handler(pi: &PanicInfo) { + println!("the application {}", pi); +} + +fn main() { + panic::set_hook(Box::new(panic_handler)); + + panic!("Hello, {}!", "world"); +} +``` + +Would print: + +``` console +$ cargo run +the application panicked at 'Hello, world!', src/main.rs:27:4 +``` + +## `#[panic_implementation]` + +A `#[panic_implementation]` attribute will be added to the language. This +attribute can be used to specify the behavior of `panic!` in no-std context. +Only functions with signature `fn(&PanicInfo) -> !` can be annotated with this +attribute, and only one item can be annotated with this attribute in the whole +dependency graph of a crate. + +Here's an example of how to replicate the panic messages one gets on std +programs on a no-std program: + +``` rust +use core::fmt; +use core::panic::PanicInfo; + +// prints: "program panicked at 'reason', src/main.rs:27:4" +#[panic_implementation] +fn my_panic(pi: &PanicInfo) -> ! { + let _ = writeln!(&MY_STDERR, "program {}", pi); + + abort() +} +``` + +The `#[panic_implementation]` item will roughly expand to: + +``` rust +fn my_panic(pi: &PanicInfo) -> ! { + // same as before +} + +// Generated by the compiler +// This will always use the correct ABI and will work on the stable channel +#[lang = "panic_fmt"] +#[no_mangle] +pub extern fn rust_begin_panic(msg: ::core::fmt::Arguments, + file: &'static str, + line: u32, + col: u32) -> ! { + my_panic(&PanicInfo::__private_unstable_constructor(msg, file, line, col)) +} +``` + +## Payload + +The `core` version of the `panic!` macro will gain support for *payloads*, as in +`panic!(42)`. When invoked with a payload `PanicInfo.payload()` will return the +payload as an `&Any` trait object just like it does in std context with custom +panic hooks. + +When using `core::panic!` with formatting, e.g. `panic!("{}", 42)`, the payload +will be uninspectable: it won't be downcastable to any known type. This is where +`core::panic!` diverges from `std::panic!`. The latter returns a `String`, +behind the `&Any` trait object, from the `payload()` method in this situation. + +## Feature gate + +The initial implementation of the `#[panic_implementation]` mechanism as well as +the `core::panic::Location` and `core::panic::PanicInfo` types will be feature +gated. `std::panic::Location` and `std::panic::PanicInfo` will continue to be +stable except for the new `PanicInfo.message` method. + +## Unwinding + +The `#[panic_implementation]` mechanism can only be used with no-std +applications compiled with `-C panic=abort`. Applications compiled with `-C +panic=unwind` additionally require the `eh_personality` language item which this +proposal doesn't cover. + +## `std::panic!` + +This proposal doesn't affect how the selection of the panic runtime in `std` +applications works (`panic_abort`, `panic_unwind`, etc.). Using +`#[panic_implementation]` in `std` programs will cause a compiler error. + +# How We Teach This +[how-we-teach-this]: #how-we-teach-this + +Currently, no-std applications are only possible on nightly so there's not much +official documentation on this topic given its dependency on several unstable +features. Hopefully once no-std applications are minimally possible on stable we +can have a detailed chapter on the topic in ["The Rust Programming Language"] +book. In the meantime, this feature can be documented in [the unstable book]. + +["The Rust Programming Language"]: https://doc.rust-lang.org/book/second-edition/ +[the unstable book]: https://doc.rust-lang.org/unstable-book/ + +# Drawbacks +[drawbacks]: #drawbacks + +## Slight deviation from std + +Although both `#[panic_implementation]` (no-std) and custom panic hooks (std) +use the same `PanicInfo` type. The behavior of the `PanicInfo.payload()` method +changes depending on which context it is used: given `panic!("{}", 42)`, +`payload()` will return a `String`, behind an `Any` trait object, in std context +but it will return an opaque `Any` trait object in no-std context. + +# Alternatives +[alternatives]: #alternatives + +## Not doing this + +Not providing a stable alternative to the `panic_fmt` language item means that +no-std applications will continue to be tied to the nightly channel. + +## Two `PanicInfo` types + +An alternative design is to have two different `PanicInfo` types, one in `core` +and one in `std`. The difference between these two types would be in their APIs: + +``` rust +// core +impl PanicInfo { + pub fn location(&self) -> Option { .. } + pub fn message(&self) -> Option<&fmt::Arguments> { .. } + + // Not available + // pub fn payload(&self) -> &(Any + Send) { .. } +} + +// std +impl PanicInfo { + pub fn location(&self) -> Option { .. } + pub fn message(&self) -> Option<&fmt::Arguments> { .. } + pub fn payload(&self) -> &(Any + Send) { .. } +} +``` + +In this alternative design the signature of the `#[panic_implementation]` +function would be enforced to be `fn(&core::panic::PanicInfo) -> !`. Custom +panic hooks will continue to use the `std::panic::PanicInfo` type. + +This design precludes supporting payloads in `core::panic!` but also eliminates +the difference between `core::PanicInfo.payload()` in no-std vs std by +eliminating the method in the former context. + +# Unresolved questions +[unresolved]: #unresolved-questions + +## `fmt::Display` + +Should the `Display` of `PanicInfo` format the panic information as `"panicked +at 'reason', src/main.rs:27:4"`, as `"'reason', src/main.rs:27:4"`, or simply as +`"reason"`. + +## Unwinding in no-std + +Is this design compatible, or can it be extended to work, with unwinding +implementations for no-std environments?