rust-lang / rust-lang/rust

Associated type bounds are not equivalent to de-sugared `where` clauses for supertraits and nested associated types

Open
#130,805 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-trait-system T-types
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

I am not sure if this is a bug or a documentation issue.

The Rust Reference states:

  • In trait declarations as supertraits: trait Circle: Shape {} is equivalent to trait Circle where Self: Shape {}
  • In trait declarations as bounds on associated types: trait A { type B: Copy; } is equivalent to trait A where Self::B: Copy { type B; }

This matches the associated type bounds RFC:

  • The surface syntax T: Trait<AssociatedType: Bounds> should desugar to a pair of bounds: T: Trait and <T as Trait>::AssociatedType: Bounds.
  • The new syntax does not introduce any new semantics.

The stabilization PR says almost the same thing, but with a subtle difference:

  • […] where T: Trait<Assoc: Bound> is equivalent to where T: Trait, <T as Trait>::Assoc: Bound.
  • Supertraits - Similar to above, trait CopyIterator: Iterator<Item: Copy> {}. This is almost equivalent to breaking up the bound into two (or more) where clauses; however, the bound on the associated item is implied whenever the trait is used. See Should associated type bounds on supertraits be implied? #112573/Make associated type bounds in supertrait position implied #112629.
  • Associated type item bounds - This allows constraining the nested rigid projections that are associated with a trait's associated types. e.g. trait Trait { type Assoc: Trait2<Assoc2: Copy>; }.

(Emphasis mine.)

However, the PR linked in that second bullet, Make associated type bounds in supertrait position implied, implies that this where clause implies the same bounds as the B<Assoc: C> syntax:

trait A: B<Assoc: C> {} should be able to imply both Self: B and <Self as B>::Assoc: C.

For normal associated types, this is definitely the case, but for bounds on associated types of supertraits, and bounds on associated types of associated types, these two forms are demonstrably not equivalent:

This compiles:

// A trait which has bounds on a nested associated type,
// using shorthand associated type bounds syntax.
// Associated type bounds ARE implied elsewhere.
pub trait NestedSugar
{
    // Sufficient.
    type Output: Iterator<Item: Clone>;
    fn make() -> Self::Output;
}

pub fn nested_sugar<T>() -> <T::Output as Iterator>::Item
where
    T: NestedSugar,
    // `No <T::Output as Iterator>::Item: Clone` required.
{
    T::make().next().unwrap().clone()
}

And this compiles:

// Supertrait with bounds on the associated type of the subtrait,
// using shorthand associated type bounds syntax.
// Supertrait associated type bounds ARE implied elsewhere.
pub trait SuperSugar
where
    Self: Iterator<Item: PartialEq>,
{
    fn super_next_sugar(&self) -> <Self as Iterator>::Item;
}

pub fn take_sugar<I>(iter: I) -> bool
where
    I: SuperSugar,
    // No `<I as Iterator>::Item: PartialEq` required.
{
    let first = iter.super_next_sugar();
    let second = iter.super_next_sugar();
    first == second
}

But this does not compile:

// A trait which has bounds on a nested associated type,
// using a where clause.
// The associated type bounds are NOT implied elsewhere.
pub trait NestedWhere
where
    Self::Output: Iterator,
    // Not sufficient.
    <Self::Output as Iterator>::Item: Clone,
{
    type Output;
    // GAT-ish syntax is also not sufficient:
    //type Output: Iterator where <Self::Output as Iterator>::Item: Clone;
    
    fn make() -> Self::Output;
}

pub fn nested_where<T>() -> <T::Output as Iterator>::Item
where
    T: NestedWhere,
    // Required. Does not compile without this line:
    //<T::Output as Iterator>::Item: Clone,
    // error[E0277]: the trait bound `<<T as NestedWhere>::Output as Iterator>::Item: Clone` is not satisfied
{
    T::make().next().unwrap().clone()
}

Nor does this:

// Supertrait with bounds on the associated type of the subtrait,
// using a where clause.
// Associated type bounds are NOT implied elsewhere.
pub trait SuperWhere
where
    Self: Iterator,
    // Not sufficient.
    <Self as Iterator>::Item: PartialEq,
{
    fn super_next_where(&self) -> <Self as Iterator>::Item;
}

pub fn take_where<I>(iter: I) -> bool
where
    I: SuperWhere,
    // Required. Does not compile without this line:
    //<I as Iterator>::Item: PartialEq,
    // error[E0277]: can't compare `<I as Iterator>::Item` with `<I as Iterator>::Item`
    // help: the trait `PartialEq` is not implemented for `<I as Iterator>::Item`
{
    let first = iter.super_next_where();
    let second = iter.super_next_where();
    first == second
}

Playground

Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=faec69dd74703073da0a6183cceb9afd

See also

All of those issues talk about type bounds not being implied in one case or another, but as far as I can tell there is no issue about the discrepancy of implied bounds between these two syntaxes. And again I am not sure if this is a bug or a documentation (and possibly diagnostics) issue.

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 by running the linked Rust Playground examples and compare the shorthand associated type bounds with the corresponding where clauses. Read the Rust Reference, associated type bounds RFC, and stabilization PR, then use the linked issues to establish whether the discrepancy is intended; done means a decided behavior with matching semantics or clarified documentation and diagnostics.

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
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.