rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.23k stars 12.7k forks source link

`where` on `trait` declaration should be provided when the trait is an input bound, but is instead a requirement #103387

Open CAD97 opened 2 years ago

CAD97 commented 2 years ago

I tried this code: [playground]

pub trait Trait {}

pub trait WithAssoc {
    type Assoc;
}

pub trait WithSizedAssoc: WithAssoc
where
    Self::Assoc: Trait,
{
}

pub struct S<T: WithSizedAssoc>(T::Assoc);

I expected this to compile. Instead, it gives the following error:

error[[E0277]](https://doc.rust-lang.org/stable/error-index.html#E0277): the trait bound `<T as WithAssoc>::Assoc: Trait` is not satisfied
  --> src/lib.rs:13:17
   |
13 | pub struct S<T: WithSizedAssoc>(T::Assoc);
   |                 ^^^^^^^^^^^^^^ the trait `Trait` is not implemented for `<T as WithAssoc>::Assoc`
   |
note: required by a bound in `WithSizedAssoc`
  --> src/lib.rs:9:18
   |
7  | pub trait WithSizedAssoc: WithAssoc
   |           -------------- required by a bound in this
8  | where
9  |     Self::Assoc: Trait,
   |                  ^^^^^ required by this bound in `WithSizedAssoc`
help: consider introducing a `where` clause, but there might be an alternative better way to express this requirement
   |
13 | pub struct S<T: WithSizedAssoc>(T::Assoc) where <T as WithAssoc>::Assoc: Trait;
   |                                           ++++++++++++++++++++++++++++++++++++

For more information about this error, try `rustc --explain E0277`.

Meta

playground 1.66.0-nightly (2022-10-21 5c8bff74bc1c52bef0c7)

compiler-errors commented 2 years ago

The only trait bounds that are implied ("elaborated" in rustc terminology) are supertrait bounds or Self: Trait bounds, so I'm not sure if this is right to be considered a bug.

CAD97 commented 2 years ago

I agree that this veers close to being a new feature rather than a bug. However, since where Self: Trait bounds are implied/elaborated, I personally think the same should be true of all where bounds on the trait itself.

And as I just found out, nightly trait aliases do provide elaborated bounds for non-Self bounds in their where:

#![feature(trait_alias)]

pub trait Trait {}

pub trait WithAssoc {
    type Assoc;
}

pub trait WithBoundAssoc = WithAssoc where <Self as WithAssoc>::Assoc: Trait;
// pub trait WithBoundAssoc: WithAssoc where Self::Assoc: Trait {}

pub struct S<T: WithBoundAssoc>(T::Assoc);

Obviously I think the trait alias behavior is more intuitive.

using that for a workaround ```rust #[cfg(trait_alias)] pub trait WithBoundAssoc = WithAssoc where ::Assoc: Trait; #[doc(hidden)] #[cfg(not(trait_alias)] pub trait WithBoundAssoc: WithAssoc { type __Assoc: Trait; } #[doc(hidden)] #[cfg(not(trait_alias)] impl WithBoundAssoc for T where Self::Assoc: Trait { type __Assoc = Self::Assoc; } ``` -----

This might unfortunately be technically a breaking change to the language, though, since because the where is not elaborated currently, it's technically not a breaking library change to remove a (non-Self) where bound from a trait declaration.

@rustbot label +C-feature-request

compiler-errors commented 2 years ago

I wonder if that trait alias example is a bug, lol. Or, at least, I'm not exactly sure if we should be treating where clauses on trait aliases as supertraits :thinking:

CAD97 commented 2 years ago

IIUC, where clauses are deliberately elaborated for trait aliases, because IIRC the intent is that writing <T: TraitAlias> should be equivalent to inlining the trait alias (i.e. writing the supertrait and where bounds out explicitly).

compiler-errors commented 2 years ago

Makes sense.

CAD97 commented 2 years ago

I believe this basically is a subset of the more general idea of implied bounds. Adding a bit more context,

are trait items are different from struct items w.r.t. implied/elaborated bounds? By current standard Rust API design convention, generic bounds should only be added to the `struct` definition if they're structural and thus necessary in order to define the type or its `Drop` implementation. However, it's a fairly common mistake to add bounds unnecessarily, in order to e.g. make a `#[derive]` compile. It's also not uncommon to see libraries disagree with the standard API design guildelines and add a type's "ubiquitous" bounds (i.e. those not structurally required for the definition but required for the type's construction/functionality to make sense). Implied bounds would basically be blessing such usage, and making it semver-breaking to remove (if they function across semver boundaries). `trait`, on the other hand, is *very strongly* a declaration of an interface. I'd argue that this changes the context enough that it makes sense for bounds to additionally elaborate the `trait` item's non-`Self` `where` bounds, even if `struct` items don't carry implied bounds. Also, a `trait` is already listed as part of the bounds, so is somewhat less implied than bounds as part of a `struct` definition. -----

I don't know what the proper thing is to do with this issue at this point.

KiruyaMomochi commented 1 year ago

After some search I found this issue is actually aged, which can be traced back to 2015.

Related issues and questions

Inconsistency in type checking where clauses in trait definition #28055

28055 by @malbarbo is got closed in 12 hours:

This is how supertraits work. not a bug. The issue (elaboration) - this is part of the trait-system and mostly documented by the code comments. The thing is that we don't want too many bounds to be implicitly available for functions, as this can lead to fragility with distant changes causing functions to stop compiling.

However, the issue author is not convinced. His comment has received 8 👍🏼s including me.

While the description of how bounds are added to a function makes sense, and is very useful, I'm still left with the question "why?"

If I have some trait C: B where ::A: A {}, it seems that for any case where T: C could possibly be true, the other bounds would have to be true as well. Could that proof be added to the trait bounds resolver?

There is a related question on Stack Overflow. Eric Langlois gives a workaround using a helper trait.

On the other hand, this issue spawned https://github.com/rust-lang/reference/issues/504 for documenting this behavior. But until now no one has actually written the doc.

Trait bounds for generic types do not imply themselves when they restrict associated types #109325

109325 is asked recently for the same thing. Where @compiler-errors Replies with:

Unfortunately where clauses are not, in general, implied like this. Only bounds that concern the trait's Self type are implied.

My opinion

While it's possible to satisfy some user's requirements using trait alias, I agree with @CAD97 that it is a subset of implied bounds, and should be implied without explicitly writing where <T as WithAssoc>::Assoc: Trait in the end.

Coder-256 commented 2 months ago

I think this is actually a duplicate of #20671 as well; that thread mentions the same workaround too.

I just ran into this, unfortunately with GATs this time. Unfortunately the workaround doesn't seem to work with GATs for some reason (Playground):

pub trait Trait {}

pub trait WithAssoc {
    type Assoc<'a>;
}

pub trait WithBoundAssoc: for<'a> WithAssoc<Assoc<'a> = Self::__Assoc<'a>> {
    type __Assoc<'a>: Trait;
}

impl<T: WithAssoc> WithBoundAssoc for T
where
    for<'a> Self::Assoc<'a>: Trait,
{
    type __Assoc<'a> = Self::Assoc<'a>;
}
error[E0275]: overflow evaluating the requirement `Self: WithBoundAssoc`
  --> src/lib.rs:7:1
   |
7  | pub trait WithBoundAssoc: for<'a> WithAssoc<Assoc<'a> = Self::__Assoc<'a>> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`playground`)
note: required for `Self` to implement `WithBoundAssoc`
  --> src/lib.rs:11:20
   |
11 | impl<T: WithAssoc> WithBoundAssoc for T
   |                    ^^^^^^^^^^^^^^     ^
12 | where
13 |     for<'a> Self::Assoc<'a>: Trait,
   |                              ----- unsatisfied trait bound introduced here
   = note: 63 redundant requirements hidden
   = note: required for `Self` to implement `WithBoundAssoc`

For more information about this error, try `rustc --explain E0275`.
error: could not compile `playground` (lib) due to 1 previous error

It does compile successfully with trait aliases (Playground):

#![feature(trait_alias)]

pub trait Trait {}

pub trait WithAssoc {
    type Assoc<'a>;
}

pub trait WithBoundAssoc = WithAssoc where for<'a> <Self as WithAssoc>::Assoc<'a>: Trait;

It seems like there's no real way to get elaborated bounds for GATs in stable Rust, which is super unfortunate. :/ Does anyone know if this is possible? Is the "overflow evaluating the requirement" error a bug?