Panic macro consistency - The Rust Edition Guide

## Keyboard shortcuts

Press `←` or `→` to navigate between chapters

Press `S` or `/` to search in the book

Press `?` to show this help

Press `Esc` to hide this help

# Panic macro consistency

## Summary

- `panic!(..)` now always uses `format_args!(..)`, just like `println!()`.
- `panic!("{")` is no longer accepted, without escaping the `{` as `{{`.
- `panic!(x)` is no longer accepted if `x` is not a string literal.
  - Use `std::panic::panic_any(x)` to panic with a non-string payload.
  - Or use `panic!("{}", x)` to use `x`’s `Display` implementation.
- The same applies to `assert!(expr, ..)`.

## Details

The `panic!()` macro is one of Rust’s most well known macros. However, it has some subtle surprises that we can’t just change due to backwards compatibility.

    // Rust 2018
    panic!("{}", 1); // Ok, panics with the message "1"
    panic!("{}"); // Ok, panics with the message "{}"

The `panic!()` macro only uses string formatting when it’s invoked with more than one argument. When invoked with a single argument, it doesn’t even look at that argument.

    // Rust 2018
    let a = "{";
    println!(a); // Error: First argument must be a format string literal
    panic!(a); // Ok: The panic macro doesn't care

It even accepts non-strings such as `panic!(123)`, which is uncommon and rarely useful since it produces a surprisingly unhelpful message: `panicked at 'Box<Any>'`.

This will especially be a problem once implicit format arguments are stabilized. That feature will make `println!("hello {name}")` a short-hand for `println!("hello {}", name)`. However, `panic!("hello {name}")` would not work as expected, since `panic!()` doesn’t process a single argument as format string.

To avoid that confusing situation, Rust 2021 features a more consistent `panic!()` macro. The new `panic!()` macro will no longer accept arbitrary expressions as the only argument. It will, just like `println!()`, always process the first argument as format string. Since `panic!()` will no longer accept arbitrary payloads, `panic_any()` will be the only way to panic with something other than a formatted string.

    // Rust 2021
    panic!("{}", 1); // Ok, panics with the message "1"
    panic!("{}"); // Error, missing argument
    panic!(a); // Error, must be a string literal

In addition, `core::panic!()` and `std::panic!()` will be identical in Rust 2021. Currently, there are some historical differences between those two, which can be noticeable when switching `#![no_std]` on or off.

## Migration

A lint, `non_fmt_panics`, gets triggered whenever there is some call to `panic` that uses some deprecated behavior that will error in Rust 2021. The `non_fmt_panics` lint has already been a warning by default on all editions since the 1.50 release (with several enhancements made in later releases). If your code is already warning free, then it should already be ready to go for Rust 2021!

You can automatically migrate your code to be Rust 2021 Edition compatible or ensure it is already compatible by running:

    cargo fix --edition

Should you choose or need to manually migrate, you’ll need to update all panic invocations to either use the same formatting as `println` or use `std::panic::panic_any` to panic with non-string data.

For example, in the case of `panic!(MyStruct)`, you’ll need to convert to using `std::panic::panic_any` (note that this is a function not a macro): `std::panic::panic_any(MyStruct)`.

In the case of panic messages that include curly braces but the wrong number of arguments (e.g., `panic!("Some curlies: {}")`), you can panic with the string literal by either using the same syntax as `println!` (i.e., `panic!("{}", "Some curlies: {}")`) or by escaping the curly braces (i.e., `panic!("Some curlies: {{}}")`).