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
- Do not combine a reservation impl with associated items on the same trait; split the trait or remove the reservation polarity.
- Avoid projecting the associated item; compute the value through a separate where-bound or helper trait without a reservation impl.
- Run on the classic (old) solver instead: drop '-Znext-solver' / '-Ztrait-solver=next' from RUSTFLAGS.
- 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
- Do not mix reservation impls with associated items on the same trait.
- Prefer the classic solver when experimenting with unstable trait features.
- Track the trait-system-refactor-initiative issues before relying on reservation impls.
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
- Accessing live loans requires `-Zpolonius=next`
- `async gen` closures not supported yet
- AsyncFn* are not yet const
- Can't have a type error relating to itself
- coroutine lowered from async gen fn should be in fn
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 unnecessarilyView on GitHub (pinned to 7088e4b63a)