rust-lang/rust · error
an interpreter error got improperly discarded; use `discard_
Error message
an interpreter error got improperly discarded; use `discard_err()` if this is intentional
What it means
`InterpResult<'tcx, T>` is a newtype around `Result` plus a `Guard` whose `Drop` impl panics with this message if the `InterpResult` is dropped without being explicitly consumed. The design forces every interpreter call site to either propagate, match, or deliberately discard its result — silently dropping a UB/invalidity error would hide miscompilation. The panic is suppressed automatically when the thread is already panicking to avoid double-panic aborts.
Source
Thrown at compiler/rustc_middle/src/mir/interpret/error.rs:933
#[macro_export]
macro_rules! throw_exhaust {
($($tt:tt)*) => { do yeet $crate::err_exhaust!($($tt)*) };
}
#[macro_export]
macro_rules! throw_machine_stop {
($($tt:tt)*) => { do yeet $crate::err_machine_stop!($($tt)*) };
}
/// Guard type that panics on drop.
#[derive(Debug)]
struct Guard;
impl Drop for Guard {
fn drop(&mut self) {
// We silence the guard if we are already panicking, to avoid double-panics.
if !std::thread::panicking() {
panic!(
"an interpreter error got improperly discarded; use `discard_err()` if this is intentional"
);
}
}
}
/// The result type used by the interpreter. This is a newtype around `Result`
/// to block access to operations like `ok()` that discard UB errors.
///
/// We also make things panic if this type is ever implicitly dropped.
#[derive(Debug)]
#[must_use]
pub struct InterpResult<'tcx, T = ()> {
res: Result<T, InterpErrorInfo<'tcx>>,
guard: Guard,
}
impl<'tcx, T> ops::Try for InterpResult<'tcx, T> {View on GitHub (pinned to 22057b88b0)
Solutions
- Propagate the result with `?` (the idiomatic fix): add `?` to the offending call.
- If the error is genuinely to be ignored, call `.discard_err()` explicitly to document intent and defuse the guard.
- Match on the result if you need branch-specific handling: `match res { Ok(_) => ..., Err(e) => ... }`.
- Use the `#[track_caller]`/backtrace from the panic to jump to the exact dropped value.
Example fix
// before ecx.write_scalar(val, dest); // InterpResult dropped → panic on drop // after ecx.write_scalar(val, dest)?; // or, if intentionally discarding: ecx.write_scalar(val, dest).discard_err();
Defensive patterns
Strategy: validation
Validate before calling
// InterpResult panics on drop if it still holds an InterpError -- the Guard
// fires. Always consume the result: propagate with `?`, or explicitly drop
// the error with `.discard_err()` when you really intend to ignore it.
fn run_block<'tcx, T>(
interp: &mut InterpCx<'tcx>,
) -> InterpResult<'tcx, T> {
let res: InterpResult<'tcx, T> = interp.do_step();
// NEVER let an InterpResult value go out of scope unhandled.
res
}
// If you genuinely want to swallow a benign error:
let _ = interp.do_step().discard_err(); // explicit, auditable Try / catch
// Prefer fixing the call site over catching. catch_unwind works but hides UB.
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let r: InterpResult<'tcx, ()> = interp.do_step();
r.discard_err(); // make the drop well-defined if you truly intend to ignore
})); Prevention
- Never store an InterpResult in a field or local and forget to consume it; #[must_use] does not catch all drop paths.
- Make `?` the default way to propagate; any place that binds InterpResult without `?` deserves a second look.
- If an error must be ignored, call .discard_err() at the exact site with a comment explaining why -- the Guard exists precisely to forbid silent drops.
- Deny `let _ = interp_result` patterns in review; require either `?`, match, or an explicit discard_err().
When it happens
Trigger: Letting an `InterpResult` value fall out of scope without `?`, `match`, `.into()`, or `.discard_err()`. Concrete shapes: `ecx.read_scalar(...);` (statement-discarded), `let _ = ecx.mplace_access(...);`, returning from a function that forgets to propagate an interp result, or storing an `InterpResult` in a field that is later dropped instead of matched.
Common situations: Refactoring an interpreter intrinsic/shim and forgetting a `?`; writing a new Miri foreign-item hook whose body drops the result of an inner call; cargo build of rustc itself after a `InterpErrorInfo` refactor; new contributors to const-eval who treat `InterpResult` like a plain `Result` and call `.ok()`.
Related errors
- range should be nonempty
- there must be provenance somewhere here
- got a pointer where a ScalarInt was expected
- statics should not have generic parameters
- destructed mir constant of adt without variant idx
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/1cef56a8327b8a90.json.
Report an issue: GitHub.