rust-lang/rust · error · PanicNonStrErr

argument to `panic!()` in a const context must have type `&s

Error message

argument to `panic!()` in a const context must have type `&str`

What it means

Reported by the const-eval checker via the PanicNonStr op whenever the interpreter encounters a call to the panic lang item inside a const/static/const-fn context and the first argument is not a &str. In const eval only the trivial panic!("literal") / panic!(str_expr) form is allowed; formatted panics and panics with non-string payloads are rejected because the const-eval panic machinery cannot format or carry arbitrary payloads at compile time.

Source

Thrown at compiler/rustc_const_eval/src/diagnostics.rs:135

#[note("at compile-time, pointers do not have an integer value")]
#[note(
    "avoiding this restriction via `transmute`, `union`, or raw pointers leads to compile-time undefined behavior"
)]
pub(crate) struct RawPtrToIntErr {
    #[primary_span]
    pub span: Span,
}

#[derive(Diagnostic)]
#[diag("pointers cannot be reliably compared during const eval")]
#[note("see issue #53020 <https://github.com/rust-lang/rust/issues/53020> for more information")]
pub(crate) struct RawPtrComparisonErr {
    #[primary_span]
    pub span: Span,
}

#[derive(Diagnostic)]
#[diag("argument to `panic!()` in a const context must have type `&str`")]
pub(crate) struct PanicNonStrErr {
    #[primary_span]
    pub span: Span,
}

#[derive(Diagnostic)]
#[diag(r#"function pointer calls are not allowed in {$kind}s"#)]
pub(crate) struct UnallowedFnPointerCall {
    #[primary_span]
    pub span: Span,
    pub kind: ConstContext,
}

#[derive(Diagnostic)]
#[diag("`{$def_path}` is not yet stable as a const fn")]
pub(crate) struct UnstableConstFn {
    #[primary_span]
    pub span: Span,

View on GitHub (pinned to 22057b88b0)

Solutions

  1. Replace the panic with a &str literal or a const &str expression
  2. For formatted messages, pre-format at runtime instead of const, or use a static string with no interpolation
  3. Replace panic! with const-friendly const_panic crate patterns if the use case really needs it
  4. If the panic comes from a macro, expand it and pass a plain &str

Example fix

// before
const N: u32 = if FLAG { 1 } else { panic!("bad: {}", 0) };
// after
const N: u32 = if FLAG { 1 } else { panic!("FLAG must be set") };
Defensive patterns

Strategy: type-guard

Validate before calling

// const-eval requires panic!() message to be &str. Pre-check panic! argument
// types with a build-script / clippy-style pass, or statically in your source:
//
// In const fn, only use string literals:
//   const fn f(x: i32) -> i32 {
//       if x < 0 { panic!("negative"); }   // OK: &str literal
//   }
//
// Forbidden in const context:
//   panic!(format!("neg: {}", x));   // String, not &str
//   panic!("{}", x);                // formatting args not allowed

Type guard

// AST-level guard for macros generating const fn bodies: reject non-&str panic payloads.
// In procedural-macro land:
enum PanicPayload {
    StrLit(String),     // accepted in const context
    FormatArgs,         // rejected in const context
    Expr,               // rejected in const context
}

fn classify_panic_payload(mac: &syn::Macro) -> PanicPayload {
    if mac.tokens.is_empty() {
        return PanicPayload::StrLit(String::new());
    }
    // single string literal token -> &str
    let mut tts = mac.tokens.clone().into_iter();
    if let Some(tt::TokenTree::Literal(lit)) = tts.next() {
        let s = lit.to_string();
        if s.starts_with('"') && s.ends_with('"') && tts.next().is_none() {
            return PanicPayload::StrLit(s);
        }
    }
    if mac.path.is_ident("format") || mac.path.is_ident("format_args") {
        return PanicPayload::FormatArgs;
    }
    PanicPayload::Expr
}

fn is_const_safe_panic(mac: &syn::Macro) -> bool {
    matches!(classify_panic_payload(mac), PanicPayload::StrLit(_))
}

Try / catch

// This is a compile-time type error; there is no runtime catch. Use a build.rs
// or a lint pass that fails fast:
fn reject_non_str_const_panic(crate_src: &str) -> Result<(), String> {
    let file = syn::parse_file(crate_src).map_err(|e| e.to_string())?;
    for item in &file.items {
        if let syn::Item::Fn(f) = item {
            let is_const = f.sig.constness.is_some();
            for stmt in &f.block.stmts {
                // walk and find panic!() macro invocations; left as exercise for the
                // real linter; on match: if !is_const_safe_panic(&mac) && is_const { return Err(...) }
                let _ = (stmt, is_const);
            }
        }
    }
    Ok(())
}

Prevention

When it happens

Trigger: rustc_const_eval::check_consts evaluates a const item (const, static, const fn body, array length, inline const block) and hits R::PanicNonStr, i.e. a panic!() call whose first argument's type is not &str. Examples: panic!(0u32), panic!("{}", x), panic!(some_int), panic!(format!(...)), or a macro expanding to such a panic.

Common situations: Refactoring a runtime panic to compile-time; using assert! in a const context where the message formats a value; stabilizing code from runtime into const fn; depending on a macro that emits panic! with a non-string first arg.

Related errors


AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03). Data as JSON: /data/errors/2407d625703075e1.json. Report an issue: GitHub.