Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Qyriad opened this issue Sep 24, 2024 · 1 comment
Labels
A-trait-system Area: Trait system T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@Qyriad
Copy link

Qyriad commented Sep 24, 2024

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.

@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Sep 24, 2024
@compiler-errors
Copy link
Member

The first failing (NestedWhere) is fixed by #120752.

The second example (SuperWhere) is not, and could be considered a bug, though it's somewhat different in character. In the absence of a bug, though, this discrepancy is a underspecification of the reference, which doesn't explain how supertraits and item bounds interact with associated type bounds.

@workingjubilee workingjubilee added A-trait-system Area: Trait system T-types Relevant to the types team, which will review and decide on the PR/issue. labels Sep 24, 2024
@Noratrieb Noratrieb removed the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Nov 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-trait-system Area: Trait system T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

5 participants