2
votes

I have a trait which manages conversions to different trait objects. The trait looks like this: (Boo and Gee are both different Traits)

trait Foo {
    fn as_boo(&mut self) -> Option<&mut Boo> {
        None
    }

    fn as_gee(&mut self) -> Option<&mut Gee> {
        None
    }
}

To reduce the amount of boilerplate code I now want to automatically change this implementation to this in case the struct implements Boo/Gee:

#[derive(Boo)]
struct W {}

impl Foo for W {
    fn as_boo(&mut self) -> Option<&mut Boo> {
        Some(self)
    }
}

I could do this if there is only one other trait I have to convert to by using generics:

impl<T: Boo> Foo for T {
    fn as_boo(&mut self) -> Option<&mut Boo> {
        Some(self)
    }
}

But in case I also want to automatically implement Foo for Gee it doesn't work this way due to the fact that I could not implement Foo twice, which is not supported by rust as far as I know.

// This does not compile because Foo might get implemented twice.
impl<T: Boo> Foo for T {
    fn as_boo(&mut self) -> Option<&mut Boo> {
        Some(self)
    }
}

impl<T: Gee> Foo for T {
    fn as_gee(&mut self) -> Option<&mut Gee> {
        Some(self)
    }
}

It might be possible to achieve this using Procedural Macros but I could not find any in depth explanation of them so I am kind of stuck now.

1
As far as I know procedural macros don't have access to type information, so you will not be able to decide whether the type needs to override default method.red75prime

1 Answers

2
votes

This problem can be solved by introducing an extra level of indirection:

trait Boo {}
trait Gee {}

trait FooAsBoo {
    fn as_boo(&mut self) -> Option<&mut Boo> {
        None
    }
}

trait FooAsGee {
    fn as_gee(&mut self) -> Option<&mut Gee> {
        None
    }
}

trait Foo: FooAsBoo + FooAsGee {}

impl<T: Boo> FooAsBoo for T {
    fn as_boo(&mut self) -> Option<&mut Boo> {
        Some(self)
    }
}

impl<T: Gee> FooAsGee for T {
    fn as_gee(&mut self) -> Option<&mut Gee> {
        Some(self)
    }
}

impl<T: FooAsBoo + FooAsGee> Foo for T {} // if there's nothing else in Foo

struct W;
impl Boo for W {}
impl Gee for W {}

fn main() {
    let mut w = W;
    let foo = &mut w as &mut Foo;
    let boo = foo.as_boo();
}

By moving as_boo and as_gee each to their own trait, we avoid the overlapping implementations. Methods from supertraits are available in trait objects, so Foo doesn't have to redeclare as_boo and as_gee.


While this works great in cases where Boo and Gee are close to always implemented, it still requires manual implementation when this is not the case. Considering the fact that as_gee should return None in about 80% of all calls in my program this is rather unfortunate.

We can solve this by using specialization (which is not stable as of Rust 1.19, so you'll need to use the nightly compiler). We need to move the implementations of as_boo and as_gee from the trait definition to an impl that applies to all types, so that all types implement FooAsBoo and FooAsGee.

#![feature(specialization)]

trait FooAsBoo {
    fn as_boo(&mut self) -> Option<&mut Boo>;
}

trait FooAsGee {
    fn as_gee(&mut self) -> Option<&mut Gee>;
}

impl<T> FooAsBoo for T {
    default fn as_boo(&mut self) -> Option<&mut Boo> {
        None
    }
}

impl<T> FooAsGee for T {
    default fn as_gee(&mut self) -> Option<&mut Gee> {
        None
    }
}