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

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

Open
CAD97 opened this issue Oct 22, 2022 · 8 comments
Labels
C-feature-request Category: A feature request, i.e: not implemented / a PR.

Comments

@CAD97
Copy link
Contributor

CAD97 commented Oct 22, 2022

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 5c8bff7)

@CAD97 CAD97 added the C-bug Category: This is a bug. label Oct 22, 2022
@compiler-errors
Copy link
Member

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
Copy link
Contributor Author

CAD97 commented Oct 22, 2022

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
#[cfg(trait_alias)]
pub trait WithBoundAssoc = WithAssoc where <Self as WithAssoc>::Assoc: Trait;

#[doc(hidden)]
#[cfg(not(trait_alias)]
pub trait WithBoundAssoc: WithAssoc<Assoc = Self::__Assoc> {
    type __Assoc: Trait;
}

#[doc(hidden)]
#[cfg(not(trait_alias)]
impl<T: WithAssoc> 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

@rustbot rustbot added the C-feature-request Category: A feature request, i.e: not implemented / a PR. label Oct 22, 2022
@compiler-errors compiler-errors removed the C-bug Category: This is a bug. label Oct 22, 2022
@compiler-errors
Copy link
Member

compiler-errors commented Oct 22, 2022

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 🤔

@CAD97
Copy link
Contributor Author

CAD97 commented Oct 24, 2022

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
Copy link
Member

Makes sense.

@CAD97
Copy link
Contributor Author

CAD97 commented Oct 27, 2022

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
Copy link

KiruyaMomochi commented Apr 8, 2023

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 rust-lang/reference#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
Copy link
Contributor

Coder-256 commented Sep 2, 2024

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-feature-request Category: A feature request, i.e: not implemented / a PR.
Projects
None yet
Development

No branches or pull requests

5 participants