libs (panic | attributes | no_std)
Provide a stable mechanism to specify the behavior of panic!
in no-std
applications.
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.
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))
}
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.
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.
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.
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.
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.
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.
PanicInfo
typesAn 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.
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"
.
Is this design compatible, or can it be extended to work, with unwinding implementations for no-std environments?