rust-lang/rust · error

ConstEquate should not be emitted when `-Znext-solver` is ac

Error message

ConstEquate should not be emitted when `-Znext-solver` is active

What it means

The next trait solver intentionally does not implement ConstEquate (a predicate the old solver emits when unifying two unevaluated consts). evaluate_added_goals_and_make_canonical_response's goal dispatch hits PredicateKind::ConstEquate and panics, because under -Znext-solver const equality is handled via ConstEvaluatable/normalization instead. Reaching this panic means old-solver code constructed a ConstEquate predicate and routed it into the new solver.

Source

Thrown at compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs:935

                    ecx.compute_unstable_feature_goal(param_env, symbol)?
                }
                ty::PredicateKind::Subtype(predicate) => {
                    ecx.compute_subtype_goal(Goal { param_env, predicate })?
                }
                ty::PredicateKind::Coerce(predicate) => {
                    ecx.compute_coerce_goal(Goal { param_env, predicate })?
                }
                ty::PredicateKind::DynCompatible(trait_def_id) => {
                    ecx.compute_dyn_compatible_goal(trait_def_id)?
                }
                ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(term)) => {
                    ecx.compute_well_formed_goal(Goal { param_env, predicate: term })?
                }
                ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(ct)) => {
                    ecx.compute_const_evaluatable_goal(Goal { param_env, predicate: ct })?
                }
                ty::PredicateKind::ConstEquate(_, _) => {
                    panic!("ConstEquate should not be emitted when `-Znext-solver` is active")
                }
                ty::PredicateKind::NormalizesTo(predicate) => {
                    ecx.compute_normalizes_to_goal(Goal { param_env, predicate })?
                }
                ty::PredicateKind::Ambiguous => {
                    ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)?
                }
            })
        })
    }

    // Recursively evaluates all the goals added to this `EvalCtxt` to completion, returning
    // the certainty of all the goals.
    #[instrument(level = "trace", skip(self))]
    pub(super) fn try_evaluate_added_goals(
        &mut self,
    ) -> Result<Certainty, NoSolutionOrRerunNonErased> {
        for _ in 0..FIXPOINT_STEP_LIMIT {

View on GitHub (pinned to 22057b88b0)

Solutions

  1. Report the ICE; include the source location that constructed the ConstEquate predicate (visible in the backtrace).
  2. Migrate the offending type-checking call site to emit ConstEvaluatable/normalization goals instead of ConstEquate.
  3. As an end-user workaround, turn off -Znext-solver until the emitter path is fixed.
  4. Gate the triggering crate off the new solver with -Znext-solver=coherence or the crate-level solver selector.

Example fix

// before (old-solver emission leaking into next-solver)
self.tcx().predicate_must_hold(modulo_regions, ObligationCause::new(span, ConstEquate(a, b)));

// after (route through ConstEvaluatable / normalization)
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)?;
Defensive patterns

Strategy: validation

Validate before calling

// Block builds that combine `ConstEquate`-emitting patterns with the next solver.
fn next_solver_active() -> bool {
    std::env::var("RUSTFLAGS").unwrap_or_default().contains("next-solver")
        || std::env::var("CARGO_ENCODED_RUSTFLAGS").unwrap_or_default().contains("next-solver")
}
fn const_equate_pattern_used(src: &str) -> bool {
    // crude: a where-clause of the form `N = M` on const params emits ConstEquate
    src.contains("<const N: usize>") && src.contains("where")
        && src.split('=').count() > 1
}
#[test]
fn no_const_equate_with_next_solver() {
    assert!(!(next_solver_active() && const_equate_pattern_used(include_str!("lib.rs"))));
}

Try / catch

use std::panic::{catch_unwind, AssertUnwindSafe};
let out = catch_unwind(AssertUnwindSafe(|| run_typeck(crate_ast)));
if out.is_err() { eprintln!("ConstEquate + next-solver conflict; rebuild without -Znext-solver"); std::process::exit(101); }

Prevention

When it happens

Trigger: Triggered when the new solver's compute_goal dispatch (compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs:934) receives a ty::PredicateKind::ConstEquate(_, _) predicate. This happens when type-checking code that still emits ConstEquate (e.g. certain array-pattern / const-equality unifications) runs while -Znext-solver is active.

Common situations: ICE seen on code that equates generic consts (array sizes, const generics in patterns) when the compiler is built/run with -Znext-solver but a code path wasn't migrated off ConstEquate. Common during rustc development when toggling the solver on for more crates.

Related errors


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