rust-lang/rust · critical

reservation impl for trait with assoc item

Error message

reservation impl for trait with assoc item: {:?}

What it means

This panic comes from the next-generation trait solver when it tries to project (normalize) an associated type and finds that the matching impl is a *reservation* impl (ImplPolarity::Reservation) on a trait that has associated items. Reservation impls are reserved for special uses (e.g. future default impls in trait-system-refactor work); combining them with associated-type projection is not yet implemented, so the solver bails with unimplemented!() rather than producing a wrong answer.

Solutions

  1. Do not combine a reservation impl with associated items on the same trait; split the trait or remove the reservation polarity.
  2. Avoid projecting the associated item; compute the value through a separate where-bound or helper trait without a reservation impl.
  3. Run on the classic (old) solver instead: drop '-Znext-solver' / '-Ztrait-solver=next' from RUSTFLAGS.
  4. Report/file a rustc issue with the minimized reproduction; this is a known gap in the next solver.

Example fix

// before
trait Trait {
    type Assoc;
}
impl<T: ?Sized> ~const Trait for &T /* reservation-ish */ {
    type Assoc = ();
}

// after: keep reservation impl away from associated items
trait Trait {
    type Assoc;
}
impl<T: ?Sized> Trait for &T {
    type Assoc = ();
}
Defensive patterns

Strategy: validation

Validate before calling

// Before relying on projecting an associated item, check the impl polarity
// via rustdoc/nightly queries; or assert at build time that the trait has no
// reservation impl with associated items.
// (Compiler-internal; cannot be validated at runtime.)

Prevention

When it happens

Trigger: Compiling code that exercises both a reservation impl (#[const_trait] / reserved impls) and an associated item projection on the same trait under the next trait solver (RUSTFLAGS='-Znext-solver' or '-Ztrait-solver=next'). The match arm at normalizes_to.rs:272-276 hits ImplPolarity::Reservation for a goal whose alias has a projection def id.

Common situations: Nightly-only feature experiments with const traits / reservation impls, generic code that projects <T as Trait>::Assoc where Trait has a reservation impl, or running the new solver on crates that rely on unstable trait features. These are compiler-internal limitations, not user API misuse.

Related errors


AI-assisted analysis of rust-lang/rust@7088e4b63a (2026-08-10). Data as JSON: /api/errors/8fc190d57cbbddbc. Report an issue: GitHub.

Appendix: source

Thrown at compiler/rustc_next_trait_solver/src/solve/normalizes_to.rs:275

        then: impl FnOnce(&mut EvalCtxt<'_, D>, Certainty) -> QueryResultOrRerunNonErased<I>,
    ) -> Result<Candidate<I>, NoSolutionOrRerunNonErased> {
        let cx = ecx.cx();

        let alias_def_id = goal.predicate.alias.expect_projection_def_id();
        let goal_trait_ref = goal.predicate.alias.trait_ref(cx);
        let impl_trait_ref = cx.impl_trait_ref(impl_def_id);
        if !DeepRejectCtxt::relate_rigid_infer(ecx.cx())
            .args_may_unify(goal_trait_ref.args, impl_trait_ref.skip_binder().args)
        {
            return Err(NoSolution.into());
        }

        // We have to ignore negative impls when projecting.
        let impl_polarity = cx.impl_polarity(impl_def_id);
        match impl_polarity {
            ty::ImplPolarity::Negative => return Err(NoSolution.into()),
            ty::ImplPolarity::Reservation => {
                unimplemented!("reservation impl for trait with assoc item: {:?}", goal)
            }
            ty::ImplPolarity::Positive => {}
        };

        ecx.probe_trait_candidate(CandidateSource::Impl(impl_def_id)).enter(|ecx| {
            let impl_args = ecx.fresh_args_for_item(impl_def_id.into());
            let impl_trait_ref = impl_trait_ref.instantiate(cx, impl_args).skip_norm_wip();

            ecx.eq(goal.param_env, goal_trait_ref, impl_trait_ref)?;

            let where_clause_bounds = cx
                .clauses_of(impl_def_id.into())
                .iter_instantiated(cx, impl_args)
                .map(Unnormalized::skip_norm_wip)
                .map(|clause| goal.with(cx, clause));
            ecx.add_goals(GoalSource::ImplWhereBound, where_clause_bounds)?;

            // Bail if the nested goals don't hold here. This is to avoid unnecessarily

View on GitHub (pinned to 7088e4b63a)