4

I have the following trait:

trait MyTrait {
    type A;
    type B;

    fn foo(a: Self::A) -> Self::B;

    fn bar(&self);
}

There are other functions like bar that must be always implemented by the user of the trait.

I would like to give foo a default implementation, but only when the type A = B.

Pseudo-Rust code:

impl??? MyTrait where Self::A = Self::B ??? {
    fn foo(a: Self::A) -> Self::B {
        a
    }
}

This would be possible:

struct S1 {}

impl MyTrait for S1 {
    type A = u32;
    type B = f32;

    // `A` is different from `B`, so I have to implement `foo`
    fn foo(a: u32) -> f32 {
        a as f32
    }

    fn bar(&self) {
        println!("S1::bar");
    }
}

struct S2 {}

impl MyTrait for S2 {
    type A = u32;
    type B = u32;

    // `A` is the same as `B`, so I don't have to implement `foo`,
    // it uses the default impl

    fn bar(&self) {
        println!("S2::bar");
    }
}

Is that possible in Rust?

michalsrb
  • 4,703
  • 1
  • 18
  • 35
  • While trying to answer, I ran into [another question](https://stackoverflow.com/questions/55630220/conflicting-implementations-of-trait) which might be of interest. – Jmb Apr 11 '19 at 10:25
  • Does the function in trait which doesn't either take `self` (or reference to it) or return `Self` ever make sense? – Cerberus Apr 12 '19 at 03:05
  • @Cerberus I guess it wouldn't, but this was just simplified example. I will edit it to make more sense. – michalsrb Apr 13 '19 at 11:22

3 Answers3

5

You can provide a default implementation in the trait definition itself by introducing a redundant type parameter:

trait MyTrait {
    type A;
    type B;

    fn foo<T>(a: Self::A) -> Self::B
    where
        Self: MyTrait<A = T, B = T>,
    {
        a
    }
}

This default implementation can be overridden for individual types. However, the specialized versions will inherit the trait bound from the definition of foo() on the trait so you can only actually call the method if A == B:

struct S1;

impl MyTrait for S1 {
    type A = u32;
    type B = f32;

    fn foo<T>(a: Self::A) -> Self::B {
        a as f32
    }
}

struct S2;

impl MyTrait for S2 {
    type A = u32;
    type B = u32;
}

fn main() {
    S1::foo(42);  // Fails with compiler error
    S2::foo(42);  // Works fine
}

Rust also has an unstable impl specialization feature, but I don't think it can be used to achieve what you want.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • why did you add 'Additional methods' in the question again? Is that really important here? – hellow Apr 11 '19 at 09:41
  • @hellow It _may_ very well be important. Case in point, the fixed version of the code in the other answer actually works for the case that the aren't any further methods, but becomes cumbersome otherwise. – Sven Marnach Apr 11 '19 at 10:15
  • Thank you, but this solution doesn't work for me for the reason you already described - it is not possible to call `foo` if `A != B`, even if you provide your own implementation of `foo`. – michalsrb Apr 11 '19 at 12:03
2

Will this suffice?:

trait MyTrait {
    type A;
    type B;

    fn foo(a: Self::A) -> Self::B;
}

trait MyTraitId {
    type AB;
}

impl<P> MyTrait for P
where
    P: MyTraitId
{
    type A = P::AB;
    type B = P::AB;

    fn foo(a: Self::A) -> Self::B {
        a
    }
}

struct S2;

impl MyTraitId for S2 {
    type AB = i32;
}

Rust Playground

As noted, it'll bump into problems if MyTrait as other methods that MyTraitId can't provide an implementation for.

hellow
  • 12,430
  • 7
  • 56
  • 79
user31601
  • 2,482
  • 1
  • 12
  • 22
  • The issue is that there could be multiple `MyTraitId` implementations for any type `P`, so you would end up with multiple conflictin impls for `MyTrait`. It may be possible to get this working by making `T` an associated type of `MyTraitId` instead of a type parameter, but it would still only be a partial solution, since you will have to implement all methods of `MyTrait` in the blanket implementation. – Sven Marnach Apr 11 '19 at 09:25
  • 1
    Please don't post *untested* code. Instead use [the playground](https://play.rust-lang.org/) to easily and quickly check your code. This makes your and our life much easier :) – hellow Apr 11 '19 at 09:27
  • This would work, but what if there are other functions in the trait that do not have default implementation? Originally I had comment indicating that there are other functions, now I edited my question and added second function to make it clear. – michalsrb Apr 11 '19 at 12:15
0

Extending user31601's answer and using the remark from Sven Marnach's comment, here's an implementation of the trait with additional functions using the "delegate methods" pattern:

trait MyTrait {
    type A;
    type B;

    fn foo(a: Self::A) -> Self::B;
    fn bar();
}

trait MyTraitId {
    type AB;
    fn bar_delegate();
}

impl<P> MyTrait for P
where
    P: MyTraitId,
{
    type A = P::AB;
    type B = P::AB;

    fn foo(a: Self::A) -> Self::B {
        a
    }
    fn bar() {
        <Self as MyTraitId>::bar_delegate();
    }
}

struct S2;

impl MyTraitId for S2 {
    type AB = i32;
    fn bar_delegate() {
        println!("bar called");
    }
}

fn main() {
    <S2 as MyTrait>::bar(); // prints "bar called"
}

Playground

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Cerberus
  • 8,879
  • 1
  • 25
  • 40