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
- Replace the panic with a &str literal or a const &str expression
- For formatted messages, pre-format at runtime instead of const, or use a static string with no interpolation
- Replace panic! with const-friendly const_panic crate patterns if the use case really needs it
- 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
- In const fn, always pass a plain string literal to panic!(): panic!("msg").
- Avoid format!(), format_args!(), and interpolated values inside const fn panic! — they are not const-evaluable as &str today.
- If you need dynamic context in a const panic, encode it as a fixed set of allowed literals and select with a const if/match.
- Add a clippy::deny or a custom lint on the macro so the failure surfaces in CI before it reaches rustc_const_eval.
- Document every const fn with the constraint that panic payloads must be &str.
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
- `NumScalableVectors(0)` is illformed
- `NumScalableVectors(N)` for N>8 is illformed
- Size::bits: {bytes} bytes in bits doesn't fit in u64
- Size::add: {} + {} doesn't fit in u64
- Size::sub: {} - {} would result in negative size
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/2407d625703075e1.json.
Report an issue: GitHub.