- Feature Name: panic_implementation
- Start Date: 2017-07-19
- RFC PR: [rust-lang/rfcs#2070](https://github.com/rust-lang/rfcs/pull/2070)
- Rust Issue: [rust-lang/rust#44489](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<Location> { .. }
    pub fn message(&self) -> Option<&fmt::Arguments> { .. }

    // Not available
    // pub fn payload(&self) -> &(Any + Send) { .. }
}

// std
impl PanicInfo {
    pub fn location(&self) -> Option<Location> { .. }
    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?
