Skip to content

Generic trait with generic supertrait bound by associated type can't be used in trait bound with associated type #100177

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

Open
robinhundt opened this issue Aug 5, 2022 · 7 comments
Labels
A-diagnostics Area: Messages for errors, warnings, and lints fixed-by-next-solver Fixed by the next-generation trait solver, `-Znext-solver`. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@robinhundt
Copy link

robinhundt commented Aug 5, 2022

I'm trying to create a Channel abstraction over Sink and Stream and encountered the following issue:

Given the following (simplified without Sink and Stream) code: Playground

trait GenericTrait<T> {}

trait Channel<I>: GenericTrait<Self::T> {
    type T;
}

trait Sender {
    type Msg;

    fn send<C>()
    where
        C: Channel<Self::Msg>;
}

impl<T> Sender for T {
    type Msg = ();

    fn send<C>()
    where
        C: Channel<Self::Msg>,
    {
    }
}

// This works
fn foo<I, C>(ch: C) where C: Channel<I> {}

The current output is:

error[[E0277]](https://doc.rust-lang.org/stable/error-index.html#E0277): the trait bound `C: Channel<()>` is not satisfied
  --> src/lib.rs:18:5
   |
18 | /     fn send<C>()
19 | |     where
20 | |         C: Channel<Self::Msg>,
   | |______________________________^ the trait `Channel<()>` is not implemented for `C`
   |
help: consider further restricting this bound
   |
20 |         C: Channel<Self::Msg> + Channel<()>,
   |                               +++++++++++++

error[[E0277]](https://doc.rust-lang.org/stable/error-index.html#E0277): the trait bound `C: Channel<()>` is not satisfied
  --> src/lib.rs:20:12
   |
20 |         C: Channel<Self::Msg>,
   |            ^^^^^^^^^^^^^^^^^^ the trait `Channel<()>` is not implemented for `C`
   |
help: consider further restricting this bound
   |
20 |         C: Channel<Self::Msg> + Channel<()>,
   |                               +++++++++++++

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to 2 previous errors

The issue is present in stable, beta and nightly.

When either removing the generic parameter on GenericParameter<I>, the Self::T bound on trait Channel<I>: GenericTrait<Self::T> or the Self::Msg bound on the send method

fn send<C>()
    where
        C: Channel<Self::Msg>;

the code compiles.

I think this is at least a diagnostics bug, as the error message is quite unhelpful :D This also seems like a bug/limitation of the trait system?

This might be related/the same as #58231 or #57905, but I wasn't exactly sure.

@robinhundt robinhundt added A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Aug 5, 2022
@compiler-errors
Copy link
Member

@rustbot claim

@compiler-errors
Copy link
Member

compiler-errors commented Aug 5, 2022

The diagnostic needs to be suppressed in this case, but also I don't know why this doesn't work but it's probably due to normalization and substitution somewhere happening in the wrong order, since we clearly know what the expected/normalized param-env bound looks like 🤔

@compiler-errors compiler-errors added the T-types Relevant to the types team, which will review and decide on the PR/issue. label Aug 6, 2022
@compiler-errors
Copy link
Member

Ugh this is a really strange param env normalization bug.

@compiler-errors compiler-errors removed their assignment Aug 26, 2022
@Anders429
Copy link

@robinhundt were you able to find any workaround for this? I'm running into this exact same issue, and I can't find a way to work around it while still expressing the relationships between the traits that I want to express.

@robinhundt
Copy link
Author

@Anders429 Well, kind of.

As I wrote in the initial OP, I wanted to unify Sink and Stream in a Channel trait. What I wanted was to use Channel::T to refer to the error in a Stream of Result<Item, Error>. I noticed that the futures crate also offers the TryStream trait (link). I came up with the following:

trait Channel<Item>: Sink<Item> + TryStream<Ok = Item> {}

impl<Item, S> Channel<Item> for S where S: Sink<Item> + TryStream<Ok = Item> {}

which allows me to refer to the stream error as <Ch as TryStream>::Error. Not ideal, but it works. Unfortunately, this only works when the type we want to refer to appears in an associated type of the super trait (above, this is Stream::Item).

It appears I simplified code in the OP a bit too far. My solution with the TryStream does not work for a GenericTrait<T> as

trait GenericTrait<T> {}

trait ParamToAssoc {
    type Assoc;
}

impl<T, G> ParamToAssoc for G where G: GenericTrait<T> {
    type Assoc = T;
}

fails with an unconstrained type parameter error in the ParamToAssoc impl.

Maybe your use case is similar to mine and this helps you 😊

@robinhundt
Copy link
Author

robinhundt commented Sep 30, 2022

Well, it seems my solution of just referring to the associated type of the super trait does not work completely... 😢

trait Sink<Item> {
    type Error;

    fn foo(&mut self) -> Result<Item, Self::Error> {
        todo!()
    }
}

trait Channel<Item>: Sink<Item> {}

// This works
fn use_channel<C>(ch: &mut C) -> Result<(), C::Error>
where
    C: Channel<()>,
    <C as Sink<()>>::Error: Send,
{
    // ch.foo()
    todo!()
}

trait UsingChannel {
    type Item;

    fn use_channel<C>(ch: &mut C) -> Result<Self::Item, C::Error>
    where
        // Commenting out the following line in the declaration and the impl
        // makes the code compile
        <C as Sink<Self::Item>>::Error: Send,
        C: Channel<Self::Item>;
}

struct Foo;

// --------- The Problem is in this impl ---------

impl UsingChannel for Foo {
    type Item = ();

    fn use_channel<C>(ch: &mut C) -> Result<Self::Item, C::Error>
    where
        C: Channel<Self::Item>,
        // commenting out this line makes the code compile
        <C as Sink<Self::Item>>::Error: Send,
    {
        // ch.foo()
        todo!()
    }
}

[Playground]

the code compiles without the <C as Sink<Self::Item>>::Error: Send bound on the trait, but adding it, results in the dreaded the trait bound "C: Channel<()>" is not satisfied message as well as the trait bound "C: Sink<()>" is not satisfied.

This makes it currently impossible for me to write an abstraction over Sink + Stream, as I need some way of constraining the corresponding error types.

@robinhundt
Copy link
Author

I've tried to further reduce the issue. Unfortunately, not even the following simplified code compiles:

trait Sink<Item> {
    type Error;
}


trait UsingSink {
    type Item;

    fn use_sink<S>(ch: &mut S) -> Result<Self::Item, S::Error>
    where
        S: Sink<Self::Item>,
        // Commenting out the following line in the declaration and the impl
        // makes the code compile
        S::Error: Send;
        
}

struct Foo;

// --------- The Problem is in this impl ---------

impl UsingSink for Foo {
    type Item = ();

    fn use_sink<S>(ch: &mut S) -> Result<Self::Item, S::Error>
    where
        S: Sink<Self::Item>,
        // commenting out this line makes the code compile
        S::Error: Send,
    {
        todo!()
    }
}

[Playground]

This reduces the usability of the Sink trait (or other traits with a generic parameter and associated type), as its associated type can't be bounded in another trait when the Item generic parameter is supplied by an associated type 😢

For this simple case, a workaround exists by introducing a new generic parameter for the use_sink function, which must be equal to the Error associated type and can then be bounded. However, this is ugly and does not really work when the associated type is ambiguous, which is the case when the supertraits are Sink + TryStream.

@compiler-errors compiler-errors added the fixed-by-next-solver Fixed by the next-generation trait solver, `-Znext-solver`. label Mar 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints fixed-by-next-solver Fixed by the next-generation trait solver, `-Znext-solver`. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. 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

3 participants