rust-lang/rust · critical
no such thing as an opaque const
Error message
no such thing as an opaque const
What it means
Opaque types (`impl Trait`, async-fn returns, RPIT, TAIT) are always types, never constants. In `normalize_opaque_type` (opaque_types.rs:23), `goal.predicate.term.as_type().expect("no such thing as an opaque const")` fires if the term on the RHS of the projection goal is a const term. An opaque-type projection goal with a const RHS is meaningless, so reaching the `expect` means the solver constructed a malformed opaque-type goal — a compiler bug.
Source
Thrown at compiler/rustc_next_trait_solver/src/solve/project_goals/opaque_types.rs:23
use rustc_type_ir::solve::{GoalSource, QueryResultOrRerunNonErased, RerunReason};
use rustc_type_ir::{self as ty, Interner, MayBeErased, TypingMode, fold_regions};
use crate::delegate::SolverDelegate;
use crate::solve::{Certainty, EvalCtxt, Goal};
impl<D, I> EvalCtxt<'_, D>
where
D: SolverDelegate<Interner = I>,
I: Interner,
{
#[tracing::instrument(skip(self))]
pub(super) fn normalize_opaque_type(
&mut self,
goal: Goal<I, ty::ProjectionPredicate<I>>,
) -> QueryResultOrRerunNonErased<I> {
let cx = self.cx();
let opaque_ty = goal.predicate.projection_term;
let expected = goal.predicate.term.as_type().expect("no such thing as an opaque const");
let def_id = opaque_ty.expect_opaque_ty_def_id();
match self.typing_mode() {
TypingMode::Coherence => {
// An impossible opaque type bound is the only way this goal will fail
// e.g. assigning `impl Copy := NotCopy`
self.add_item_bounds_for_hidden_type(
def_id,
opaque_ty.args,
goal.param_env,
expected,
)?;
// Trying to normalize an opaque type during coherence is always ambiguous.
// We add a nested ambiguous goal here instead of using `Certainty::AMBIGUOUS`.
// This allows us to return the nested goals to the parent `AliasRelate` goal.
// This can then allow nested goals to fail after we've constrained the `term`.
self.add_goal(GoalSource::Misc, goal.with(cx, ty::PredicateKind::Ambiguous))?;
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)View on GitHub (pinned to 22057b88b0)
Solutions
- Report at https://github.com/rust-lang/rust/issues with the panic `no such thing as an opaque const` and a minimal `impl Trait` / async repro.
- Move the opaque type out of const positions (don't use `impl Trait` as/for an associated const).
- Disable `-Znext-solver`.
- `rustup update nightly`.
Example fix
// before — opaque in a const-ish position
const C: impl Trait = async {}.await; // nonsensical, leaks const term
// after — opaque as a type only
async fn f() -> impl Trait { /* ... */ } Defensive patterns
Strategy: type-guard
Validate before calling
// There is no such thing as an 'opaque const' — opaque types (impl Trait)
// are only valid in type position, not in const-value position.
// BAD: const X: = impl Trait; (nonsensical)
// GOOD: const X: i32 = 5;
// type O = impl Trait; (opaque in type position)
fn ensure_not_opaque_const<T>() -> bool { false } Type guard
// Reject any construction that places `impl Trait` in a const/value slot.
// Only use `impl Trait` as a return type, argument type, or type alias:
trait Trait {}
fn returns_opaque() -> impl Trait { struct Z; impl Trait for Z {} Z }
// const OPAQUE: impl Trait = ...; // <- never valid, would panic solver Prevention
- Never use `impl Trait` (or `type ... = impl Trait;`) in a const definition or const-expression position.
- Opaque types live only in type position; const values must have a concrete type annotation.
- If a macro generates opaque types, audit its output to ensure none land in const contexts.
When it happens
Trigger: An opaque-type projection goal is built whose `term` is a const (not a type), e.g. when an opaque appears in an associated-const position or const-eval context, under `-Znext-solver`.
Common situations: Nightly users putting `impl Trait` / async-fn opaque returns into associated-const or const-generic positions, or after a rustc update that mishandles opaque terms across the type/const boundary.
Related errors
- we never retry stalled queries if the parent was erased
- this never happens at the root, we're never in erased mode h
- unexpected orig_value: {ty:?}
- unexpected orig_value: {ct:?}
- ConstEquate should not be emitted when `-Znext-solver` is ac
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/9180bec71880a161.json.
Report an issue: GitHub.