Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stable mechanism to specify the behavior of panic! in no-std applications #2070

Merged
merged 2 commits into from
Sep 11, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
238 changes: 238 additions & 0 deletions text/2070-panic-implementation.md
Original file line number Diff line number Diff line change
@@ -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<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?