Skip to content

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

Open
@Qyriad

Description

@Qyriad

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-trait-systemArea: Trait systemT-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions