- Feature Name: panic_implementation
- Start Date: 2017-07-19
- RFC PR: rust-lang/rfcs#2070
- Rust Issue: rust-lang/rust#44489
Summary
Provide a stable mechanism to specify the behavior of panic!
in no-std
applications.
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.
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
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.
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.
PanicInfo
The types std::panic::PanicInfo
and std::panic::Location
will be moved
into the core
crate, and PanicInfo
will gain a new method:
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:
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:
$ 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:
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:
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
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.
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
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:
// 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
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?