Implement a trait for all types that implement another trait with multiple crates

I'm currently working on the webwire project and would like to write the following code...

I have one crate that defines the common structs and traits (simplified):

use std::collections::HashMap;

#[derive(Debug)]
struct Request {
    service: String,
    method: String,
    data: usize,
}

type Response = Result<usize, ErrorResponse>;

#[derive(Debug, Eq, PartialEq)]
enum ErrorResponse {
    NoSuchService,
    NoSuchMethod,
}

trait Service {
    fn name(&self) -> &'static str;
    fn call(&self, request: Request) -> Response;
}

Now I have another crate that is generated via some IDL file:

trait ExampleService {
    fn a(&self, data: usize) -> usize;
    fn b(&self, data: usize) -> usize;
}

impl<T: ExampleService> Service for T {
    fn name(&self) -> &'static str {
        "ExampleService"
    }
    fn call(&self, request: Request) -> Response {
        match request.method.as_str() {
            "a" => Ok(self.a(request.data)),
            "b" => Ok(self.b(request.data)),
            _ => Err(ErrorResponse::NoSuchMethod),
        }
    }
}

I haven't even started implementing the ExampleService trait and it already goes haywire:

error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g., `MyStruct<T>`)
  --> api/src/lib.rs:89:10
   |
89 |     impl<T: ExampleService> Service for T {
   |          ^ type parameter `T` must be used as the type parameter for some local type
   |
   = note: implementing a foreign trait is only possible if at least one of the types for which is it implemented is local
   = note: only traits defined in the current crate can be implemented for a type parameter

The only way I was able to solve this is by creating another type ExampleServiceAdapter and let it implement the Service trait:

pub struct ExampleServiceAdapter<T: ExampleService>(pub T);

impl<T: ExampleService + Sync + Send> Service for ExampleServiceAdapter<T> {
    fn name(&self) -> &'static str {
        "ExampleService"
    }
    fn call(&self, request: Request) -> Response {
        match request.method.as_str() {
            "a" => Ok(self.0.a(request.data)),
            "b" => Ok(self.0.b(request.data)),
            _ => Err(ErrorResponse::NoSuchMethod),
        }
    }
}

And that's basicly where I'm stuck right now. Since Service lives in another crate as ExampleService I can't implement the trait Service for all types implementing ExampleService. I do however want to keep the Service trait separate as this is code that is part of the library release and ExampleService is generated code and not part of the crate.

Adding a ExampleServiceAdapter struct just for the sake of it feels wrong and I feel like there must be a better way. I already asked in the Discord and IRC channels and learned a lot about the fine print when working with traits. I almost feel like there is nothing I can do about this. Yet I hope someone can help me design a prettier interface for this and maybe get rid of ExampleServiceAdapter in the process.

Here is some working code on Rust Playground which fails when splitting it into three different crates: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=73bdf22bb3a199160e73ea2c333e03bc

Sorry, you can't work around this. You have to use a wrapper to make it work.

Your problem here arises form what are termed Rust's orphan rules. That link provides the best summary explanation that I've seen.

I think I understand the orphan rule and the reasoning behind all this. Now the question is how do I best work around this in my code. Is it even possible to write the code so that this is actually possible.

I've been experimenting with impl Service for dyn ExampleService a bit and wonder if this could be a viable solution...

trait Service {
    fn name(&self) -> &'static str;
    fn call(&self, method: &str) -> Option<usize>;
}

trait FooService {
    fn a(&self) -> usize;
}

struct Foo {}

impl FooService for Foo {
    fn a(&self) -> usize {
        42
    }
}

impl Service for dyn FooService {
    fn name(&self) -> &'static str {
        "FooService"
    }
    fn call(&self, method: &str) -> Option<usize> {
        match method {
            "a" => Some(self.a()),
            _ => None,
        }
    }
}

fn main() {
    let foo = Foo {};
    let service: &dyn FooService = &foo;
    assert_eq!(service.call("a"), Some(42));
    assert_eq!(service.call("b"), None);
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d36c091b5d5fb845ae3cfab2d9b8163d

What’s consuming the Service interface? Without fully understanding your architecture, my instinct is to invert the control here: Give FooService a method (with default impl) that registers a closure as a callback with whatever is currently talking to generic Services.

It’s entirely possible that this doesn’t work for your application, though.

The other “normal” solution to this is writing a #[derive(...)] proc macro and defining Service as a supertrait to FooService.

The generic Service interface is used by a, yet to be written, server implementation based on hyper. The code generator creates one trait per service and I want to register the supported services with the server so clients can use the services.

I can see how inverting the control can work. That's similar to my attempt of creating an ExampleServiceAdapter. Creating instances of this type could be done by a method provided by the ExampleService trait.

While it's not pretty this seams to be the obvious choice.

I'll look into writing my own #[derive(...)] proc macro. I have been avoiding macros for now. :see_no_evil:

Since you’re doing code generation anyway, it shouldn’t be too hard— it looks like the amount of Rust parsing you’ll need is minimal; you’d mostly be spitting out a fixed string produced by your existing generator into the proc macro source.

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.