Roberto Esposito
Roberto Esposito

Reputation: 1

How to add constraint on equal types in Rust?

I have to add a constraint over a struct defined by me. In particular this struct works with a generic called T which has two types: Input and Output. I want to set a constraint on which the type of the Input is the same of the type of Output.

#[derive(Debug, Clone)]
pub struct Example<T>
{
... // fields of the struct
}

In other words since I work either with T::Input and T::Output, I have some cases in which the Output of a struct A is the Input of a struct B and clearly this is a problem in compilation time because the error I get is: expected T::Input, found T::Output so the arguments are incorrect. Actually I understand what is the problem, but I do not know how to explain what I want to do to the compiler.

I have tried to add the constraints using the where clause trying to specify that "T::Input = T::Output", but they did not work. I tried to do something like this:

#[derive(Debug, Clone)]
pub struct Example<T>
where
    T::Input: From<T::Output>,
    T::Output: From<T::Input>

{
... // fields of the struct
}

Upvotes: 0

Views: 534

Answers (2)

rodrigo
rodrigo

Reputation: 98358

As kmdreko's answer comments, the straightforward way to write this would be something like:

trait InOut {
    type Input;
    type Output;
}

struct Example<T> where T: InOut<Input = T::Output> {
    ...
}

Unfortunately this fails with that weird circular dependency error.

But sometimes, when you use an associated type in a generic type parameter, it is required to be written fully, or some ambiguity error happens. In this case, instead of T::Output you need to write <T as InOut>::Output:

trait InOut {
    type Input;
    type Output;
}

struct Example<T> where T: InOut<Input = <T as InOut>::Output> {
    ...
}

And this just works.

My guess about why the former fails and the latter works is the following: When you write T::Output the compiler does not what trait this Output comes from, so it has to check all the implemented traits, but wait... what traits does it implement? it It doesn't know, yet, so it enters an infinite loop. But if you write <T as InOut>::Output it does not have to check all the traits, it just have to implement InOut and that is already assumed when it is evaluating this condition, so all goes well.

Upvotes: 4

kmdreko
kmdreko

Reputation: 60051

Unfortunately, the naive solution doesn't work:

trait InOut {
    type Input;
    type Output;
}

struct Example<T> where T: InOut<Input = T::Output> {
    ...
}
error[E0391]: cycle detected when computing the bounds for type parameter `T`
 --> src/lib.rs:6:42
  |
6 | struct Example<T> where T: InOut<Input = T::Output> {
  |                                          ^^^^^^^^^
  |
  = note: ...which immediately requires computing the bounds for type parameter `T` again
note: cycle used when computing explicit predicates of `Example`
 --> src/lib.rs:6:42
  |
6 | struct Example<T> where T: InOut<Input = T::Output> {
  |                                          ^^^^^^^^^

However, I was able to make it work with an additional trait that implements the original trait if the types match:

trait InOut {
    type Input;
    type Output;
}

trait InAndOut: InOut<Input = Self::Item, Output = Self::Item> {
    type Item;
}

impl<T, I> InAndOut for T where T: InOut<Input = I, Output = I> {
    type Item = I;
}

struct Example<T> where T: InAndOut {
    ...
}

Full example available on the playground.

Upvotes: 1

Related Questions