Redundancy on template constraints between struct and traits implementations

Hello,

when I have a struct like this:

struct Foo<T> where T: Add<T,T> + Sub<T,T> {bar: T}

It's obvious that for all trait implementations this template constraint will still be true.

So why not remove this constraint from the traits, that is, replace:

impl<T> Add<T, T> for Foo<T> where T: Add<T,T> + Sub<T,T> {
fn add(self, rhs: T) -> T {
	self.bar + rhs
}} 

by

impl<T> Add<T, T> for Foo<T> {
    fn add(self, rhs: T) -> T {
    	self.bar + rhs
  	}
}

Currently, if I do so, I get the following errors:

error: the trait core::ops::Add<T, T> is not implemented for the type T error: the trait core::ops::Sub<T, T> is not implemented for the type T

It's not so bad in this minimal example, but it becomes really painful when there is more constraints on 'T' and Foo implements more traits. Especially as we have to copy-paste all constraints even those not used in this specific trait.

I’ve also found there to be a lot of boilerplate when implementing traits. For example:

struct Merge<A, I, J> where
    A: Ord,
    I: Iterator<A>,
    J: Iterator<A>
{
    a: Peekable<A, I>,
    b: Peekable<A, J>,
}

fn merge<A, I, J>(a: I, b: J) -> Merge<A, I, J> where
    A: Ord,
    I: Iterator<A>,
    J: Iterator<A>,
{
    Merge { a: a.peekable(), b: b.peekable() }
}

impl<A, I, J> Iterator<A> for Merge<A, I, J> where
    A: Ord,
    I: Iterator<A>,
    J: Iterator<A>
{
    // ...
}

I was hoping for a way to abstract away the type parameter and constants, maybe like the following (this is just off the top of my head, haven’t really thought it through):

typeparams MergeType<A, I, J> where
    A: Ord,
    I: Iterator<A>,
    J: Iterator<A>;

struct Merge<M: MergeType> {
{
    a: Peekable<M.A, M.I>,
    b: Peekable<M.A, M.J>,
}

I don’t think you could simply elide them from the constraint like @nicoxx suggests, as it’s possible that a trait is only implemented for a further constrained type than what the struct defines, but I think that it would help considerably if you could just share the type parameters and constrains so you could reuse them when appropriate.

To precise my thinking and if I understand your point well:

I don’t propose to elide all the constraints from traits, just the ones already specified by the struct. But if a specific trait uses another constraint, it still have to specify it.

As for you proposition, it doesn’t fully solve my concern, because you still have to define something (your ‘typeparams’).

I think it reduces the boilerplate in your example the same way that ‘Trait composition’ could reduce boilerplate in my example. But it often doesn’t make sense to create a new “set” of constraints, because this “set” has no meaning on its own, it just happens to be what is needed for a specific struct and won’t be reused.

It’s too bad if we can’t get rid of those redundancies, because it’s a step back compared to a OO language where you specify those constraints once for all.

My primary concern was about “impl Foo” and “impl TRAIT for Foo” blocks, I suppose the issue would be more complex for “external” methods.

In Haskell at least it’s considered a bad practice to put constraints on structs because a function (or trait impl) that uses that struct may not require all of the constraints (it ends up overconstrained, think of Eq for example). This way you can get rid of some boilerplate, and it could get much better with constraint aliases.

Very good point @xcv !

I must say my code is cleaner and more generic by removing the constraints on the struct and adding only the constraint needed on each “impl”.

I didn’t see this solution probably because my head is still too much Object Oriented, but It still feels weird to be able to instantiate a generic struct with any type that doesn’t really make sense.

Clearly, I still think those kind of redundancies should be removed (after all, if the feature “struct constraints” exists, then we assume that it’s sometime the best way to do the job), but it’s less important with your best practice.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.