Same trait bounds for multiple type parameters

No, it works for anything you can implement on a tuple. That doesn't include Fn though.

2 Likes

...but that doesn't help much does it? If you remove

impl MyTrait for (Foo, Bar) {}

from your example it stops compiling..

It doesn't work for anything (unless you actually want to call the trait methods on the tuple), rustc isn't smart enough to do back-inference that (A, B): Clone ⊢ A: Clone, B: Clone

fn foo<A, B>(a: A, b: B) where (A, B): Clone {
    a.clone();
    b.clone();
}

fn main() {
    foo(1, 2);
}
error[E0599]: no method named `clone` found for type parameter `A` in the current scope
error[E0599]: no method named `clone` found for type parameter `B` in the current scope
6 Likes

I guess it can't be made to, either, if we want to prevent adding impls from breaking code. Because traits are open, A: Tr, B: Tr ⊢ (A, B): Tr does not work in reverse.

Not with positive rules, certainly. But given the overlap/orphan rules, I wonder if we could allow it via negative rules -- I can't impl Clone for (NotClone, NotClone) (since tuples aren't #[fundamental]), so I'm not sure it's be possible to have a Clone tuple without the types inside being Clone.

But I know negative logic is complicated, so someone with better inference wizardry than I would be needed to say anything confidently.

Would we need to add any implementations? (A, B): Clone can only be true if A and B implement Clone right?

So the compiler could just infer that A and B are bound by Clone.

Edit: Ah, but that doesn't apply for all traits :expressionless:

So in that case I guess the current proposals are

  • Local trait aliases
    fn foo<'a, X, Y, Z, A: Tr<X>, B: Tr<Y>>(a: A, b: B)
        where Tr<T> = MyTrait<T> + Z + 'a
    
  • trait bound application on multiple parameters
    fn foo<'a, X, A: B: MyTrait<X> + 'a>(a: A, b: B)
    
  • reusable trait bounds (name?)
    fn my_fn<
        'a,
        I,
        A: Really<Item=I> + ALot<'a, I> + ToType,
        B: A, // <- B has all the same bounds as A
    >(a: A, b: B)
    { ... }
    

This looks good, but I would mark it as a trait alias, otherwise it would conflict with equality constraints between types.. Something like:

fn foo<'a, X, Y, Z, A: Tr<X>, B: Tr<Y>>(a: A, b: B)
    where trait Tr<T> = MyTrait<T> + Z + 'a

IMO this is just ugly and confusing. It's also a great way to confuse the parser

5 Likes

I don't know why that shouldn't be parsable. In my opinion it is pretty clear, but I can understand that it adds to the ambiguity of :

A third option would be back-references to already declared type parameters, e.g.:

fn my_fn<'a, I, A : Really<Item=I> + ALot<'a, I> + ToType, B : as A>(a: A, b: B)

That said, I'd personally prefer local trait aliases out of these options.

I’ve occasionally written things like this to approximate them:

trait Is { type Type; }
impl<T> Is for T { type Type = T; }

impl<A,B,TypeAlias> Something<TypeAlias> for B
where Some<Complicated<Struct<A>, B>>: Is<Type=TypeAlias>
{
    fn do_something(&self)->TypeAlias { ... }
}
1 Like

And just A: B

fn my_fn<
    'a,
    I,
    A: Really<Item=I> + ALot<'a, I> + ToType,
    B: A, // <- B has all the same bounds as A
>(a: A, b: B)
{ ... }
1 Like