PSA: [breaking-change] `panic_fmt` language item removed in favor of #[panic_implementation]


What the title says.

panic_fmt is a language item, a compiler detail, that has been with us since ever. Until recently, this language item was the only way to define what panic! does in no_std applications / crates. As of the latest nightly this language item has been removed in favor of the #[panic_implementation] attribute which offers a type checked way of declaring the behavior of panic!. #[panic_implementation] is also what we plan to stabilize in the near future.


The panic_fmt function with its “unrolled” arguments was prone to silent breakage. At some point the signature of panic_fmt changed from fn(Arguments, file: &'static str, line: u32) to fn(Arguments, file: &'static str, line: u32, col: u32); this small change didn’t cause a compiler error in crates that defined this language item but significantly increased the binary size of all downstream users of those crates.

#[panic_implementation] changes the signature of the panic handler to fn(&PanicInfo). PanicInfo is a struct that contains information about the panic! invocation, like the location of the panic (file, line, column). This struct has private fields and can be extended without causing (silent) breakage.

Finally, language items are, in general, compiler implementation details and there’s no plan to stabilize #[lang = "*"]. By turning the panic_fmt language into its own attribute we can stabilize it independently from the other language items.

Dealing with the breakage

This change is a breaking change and some action may be required from maintainers of no_std crates.

panic_fmt author

If you are the author of a crate that provides the panic_fmt language item you’ll have to update your crate to use #[panic_implementation]. Basically, you will have to go from:


use core::intrinsics;

#[lang = "panic_fmt"]
unsafe extern "C" fn panic_fmt(
    _args: core::fmt::Arguments,
    _file: &'static str,
    _line: u32,
    _col: u32,
) -> ! {



use core::intrinsics;
use core::panic::PanicInfo;

fn panic(_info: &PanicInfo) -> ! {
    unsafe { intrinsics::abort() }

Check the API documentation of PanicInfo to learn how to access the panic location and the arguments passed to panic!. Also, it may not be obvious from the API docs but formatting PanicInfo using Display will produce the familiar string: "panicked at '$reason',".

panic_fmt user

When you compile your binary crate you’ll encounter the following error:

error[E0522]: definition of an unknown language item: `panic_fmt`

Here’s how to fix it.

In the Cortex-M ecosystem, we isolated definitions of the panic_fmt language item into their own crates. If you were using any of those panic-impl crates all you have to do is bump the minor version of the dependency.

If you were depending on some other crate that provided the panic_fmt language item let the author know about the breakage; or if you feel up to the task send them a pull request!

If you encounter any issue with #[panic_implementation] please report it in the tracking issue.

This change is part of the effort towards making embedded development possible on the stable channel. (Woot! We are almost done)

Format and panic behaving unexpected on bare metal target

Awesome! I just tried it and it seems to work. Two remarks:

  1. I needed to add #![feature(panic_implementation)] because it is still an unstable feature
  2. I still needed to add the #[no_mangle] attribute, otherwise an “error: undefined symbol: rust_begin_unwind” occurs (at least when linking with LLD).


@phil_opp Thanks for testing it out. Could you report (2) in the tracking issue? (The only linker errors related to rust_begin_unwind I’ve seen seem to have been caused by the new oom lang item / allocator stuff but this was a few weeks ago)


I opened an issue, seems like you already linked it from the tracking issue.