rust-lang / rust-lang/rust

Tracking issue for where-bounds shadowing trait implementations

Open
#152,409 8 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-associated-items A-type-system C-tracking-issue T-types
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

This is a tracking issues for errors caused by where-bounds shadowing trait implementations.

trait Trait {
    type Assoc;
}
impl<T> Trait for T {
    type Assoc = i32;
}

fn where_bound_shadows_impl<T: Trait>(x: T::Assoc) -> i32 {
    x
}

This snippet results in the following error:

error[E0308]: mismatched types
 --> src/lib.rs:9:5
  |
8 | fn where_bound_shadows_impl<T: Trait>(x: T::Assoc) -> i32 {
  |                                                       --- expected `i32` because of return type
9 |     x
  |     ^ expected `i32`, found associated type
  |
  = note:         expected type `i32`
          found associated type `<T as Trait>::Assoc`
  = note: the associated type `<T as Trait>::Assoc` is defined as `i32` in the implementation, but the where-bound `T` shadows this definition
          see issue #152409 <https://github.com/rust-lang/rust/issues/152409> for more information
help: consider constraining the associated type `<T as Trait>::Assoc` to `i32`
  |
8 | fn where_bound_shadows_impl<T: Trait<Assoc = i32>>(x: T::Assoc) -> i32 {
  |                                     +++++++++++++

This subtle issue is caused by a limitation of the current implementation of the type system and we intend to fix at least some of these errors longterm.

Background

An associated type like <T as Trait>::Assoc can be replaced with the type specified in the relevant trait implementation.

trait UsingImpl {
	type Assoc;
}
impl<T> UsingImpl for T {
	type Assoc = i32;
}
fn foo<T>(x: <T as UsingImpl>::Assoc) -> i32 {
	// We can replace the associated type with `i32`
	// by looking at the impl above.
	x
}

If the corresponding trait-bound is satisfied due to a where-bound which does not specify the associated type, then we keep the associated type as is and it will only be equal to itself.

trait UsingWhereClause {
	type Assoc;
}

fn does_not_specify_assoc<T: UsingWhereClause>(x: T::Assoc) -> i32 {
	// We do not know the underlying type of `T::Assoc`, so
	// it is only equal to itself. The following line errors
	x // ERROR: mismatched types
}
fn specifies_assoc<T: UsingWhereClause<Assoc = i32>(x: T::Assoc) -> i32 {
	// We can replace the associated type with `i32`
	// as the where-bound specifies its underlying type.
	x
}

In case the corresponding trait-bound is satisfied due to both an impl and a where-clause, we ignore the underlying type provided by the impl and only consider ones specified by the where-clause. In case the where-clause does not specify the associated type, the associated type is only equal to itself, even though the impl specifies its underlying type.

trait Trait {
	type Assoc;
}
impl<T> Trait for T {
	type Assoc = i32;
}

fn does_not_specify_assoc<T: UsingWhereClause>(x: T::Assoc) -> i32 {
	// We do not specify the associated type in the where-clause.
	// Even though there is an impl which specifies the underlying type
	// of `T::Assoc`, the associated type is only equal to itself, causing
	// an error.
	x // ERROR: mismatched types
}

While there are subtle cases where this behavior is necessary and desirable, it is largely due to technical limitation of the current implementation. For an in-depth summary, see https://rustc-dev-guide.rust-lang.org/solve/candidate-preference.html#trait-where-bounds-shadow-impls. We intend to reduce the impact of this issue longterm.

How to deal with this issue

The issue is often caused by unnecessary where-bounds. In the initial example, the where-bound T: Trait is not necessary as this trait-bound can already be satisfied by using a trait implementation. The original example compiles after removing that where-bound.


It isn't always possible to remove where-bounds however. This issue can also happen due to super traits of a required where-bound.

trait Super {
    type Assoc;
}
impl<T> Super for T {
    type Assoc = u32!
}
trait Trait: Super {}

fn requires_trait<T: Trait>() {}
fn my_function<T: Trait>(x: T::Assoc) -> u32 {
    requires_trait::<T>()
    x // ERROR: mismatched types
}

While T: Super is known due the implementation, we do need the T: Trait where-bound and can't just remove it.

What we can do instead is to add a redundant where-bound explicitly specifying the associated type:

// or alternatively the more explicit `T: Trait + Super<Assoc = u32>`.
fn my_function<T: Trait<Assoc = u32>>(x: T::Assoc) -> u32 {
    requires_trait::<T>()
    x // ERROR: mismatched types
}

One example which we is a lot more challenging to work around is a blanket impl of a super trait only if a subtrait is implemented, see https://github.com/rust-lang/rust/issues/152409#issuecomment-4646353809. @danielhenrymantilla provided a workaround in https://github.com/rust-lang/rust/issues/152409#issuecomment-4659615416.


In case your issue cannot be fixed using either of these approaches, we would like to hear from you.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the minimal Rust examples in the issue and read the referenced rustc-dev-guide section on trait where-bounds shadowing impls. Trace how the current trait-solver behavior produces these diagnostics and define a focused case to address; done requires a scoped compiler change that handles the selected case without regressing the necessary shadowing behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.