Implementing a struct with Trait members that have associated traits

Still fairly new to Rust, and I can't seem to figure out how to implement the following.

// Traits
trait Widget {
    fn name(&self) -> &str;
}

trait Service {
    type Widget: Widget;
    
    fn list_widgets(&self) -> Vec<Self::Widget>;
}

// Implementation A
struct WidgetA {}
impl Widget for WidgetA {
    fn name(&self) -> &str { "WidgetA" }
}

struct ServiceA {}

impl Service for ServiceA {
    type Widget = WidgetA;
    fn list_widgets(&self) -> Vec<WidgetA> { vec![] }
}

// Implementation B
struct WidgetB {}
impl Widget for WidgetB {
    fn name(&self) -> &str { "WidgetB" }
}

struct ServiceB {}

impl Service for ServiceB {
    type Widget = WidgetB;
    fn list_widgets(&self) -> Vec<WidgetB> { vec![] }
}

// Wrapper Engines
struct EngineA<WidgetA> {
    _service: Box<dyn Service<Widget = WidgetA>>,
}

struct EngineB<WidgetB> {
    _service: Box<dyn Service<Widget = WidgetB>>,
}

struct GenericEngine<T> {
    _service: Box<dyn Service<Widget = T>>,
}

//impl<?> GenericEngine<?> {
//    fn for_service_a() -> Self {
//        Self { _service: Box::new(ServiceA {}) }
//    }
//    fn for_service_b() -> Self {
//        Self { _service: Box::new(ServiceB {}) }
//    }
//}

fn main() {
    let _enginea  = EngineA { _service: Box::new(ServiceA {}) };
    let _engineb  = EngineB { _service: Box::new(ServiceB {}) };
    let _generic1 = GenericEngine { _service: Box::new(ServiceA {}) };
    //let _generic2 = GenericEngine::for_service_a();
    //let _generic3 = GenericEngine::for_service_b();
}

Playground

Basically, I'm stumped as to how I can actually impl GenericEngine given the above associated types and constraints. Any help you guys could provide would be appreciated.

How about:

impl<T> GenericEngine<T> {
    fn for_service_a() -> Self
    where
        ServiceA: Service<Widget = T> 
    {
       Self { _service: Box::new(ServiceA {}) }
    }
    fn for_service_b() -> Self
    where
        ServiceB: Service<Widget = T> 
    {
       Self { _service: Box::new(ServiceB {}) }
    }
}

I'm not sure if this is what you actually want, though.

1 Like

My first impression is that you're trying to be overly generic, and that you're mixing being very generic with being very specific in spots.


These top two probably don't mean what you think. You're introducing a new generic paramter (WidgetA, WidgetB), so they operate just like GenericEngine. You can create an EngineB<WidgetA> for example.

struct EngineA<WidgetA> {
    _service: Box<dyn Service<Widget = WidgetA>>,
}
struct EngineB<WidgetB> {
    _service: Box<dyn Service<Widget = WidgetB>>,
}
struct GenericEngine<T> {
    _service: Box<dyn Service<Widget = T>>,
}

For the commented block, Self is an alias that includes all type parameters after they have been resolved to concrete types. If you want to return a concrete type, you need to either implement for that type

impl GenericEngine<WidgetA> {
    fn for_service_a() -> Self {
        Self { _service: Box::new(ServiceA {}) }
    }
}

...or be explicit about the types.

impl<T> GenericEngine<T> {
    fn for_service_a() -> GenericEngine<WidgetA> {
        GenericEngine { _service: Box::new(ServiceA {}) }
    }
}

But I'm still not sure this is what you're aiming for, since there can be other implementers of Service that define Widget = WidgetA than ServiceA, and you're type erasing ServiceA. If you're both type erasing something but caring a lot about what the erased type is, you probably have a design flaw.

That is, this seems more logical to me.

impl<T> GenericEngine<T> {
    fn for_service<S: Service<Widget = T> + 'static>(service: S) -> Self {
        Self { _service: Box::new(service) }
    }
}

fn main() {
    let _generic2 = GenericEngine::for_service(ServiceA {});
    let _generic3 = GenericEngine::for_service(ServiceB {});
}
2 Likes

Thank you both. I'm being generic specifically to find where the stretch points are. I come from a heavy C background, and I'm trying to come to grips with the patterns that make the most sense for more complex systems in Rust.

The specific reason for this particular exercise was ultimately to figure out how to model a system I recently completed in C#, with several entity types (based on a common ancestor), each with a set of services built around it, and an engine created using a builder pattern to include multiple services for a common entity. I wanted to see how (if at all) Rust would catch the user trying to mix services from different entity types in an engine. Both of your responses pointed me in the direction I needed.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.