Can I conditionally provide a default implementati

2019-07-20 13:50发布

问题:

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?

回答1:

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



回答2:

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.



回答3:

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.



标签: rust