1

I am new to rust and I am trying to write an app that basically uses one of many possible services to fetch some data, transform it and save to my database.

I am trying to do something like a generic interface from Java, to instantiate the correct service based on command line input and use it throughout the program.

I have coded something like this:

use anyhow::Result;

pub trait Service<T> {
    fn get_values(&self, id: u64) -> T;
    fn convert_data(&self, input: T, output: &mut Vec<MyDataStruct>) -> Result<String>;
}

and two different implementations:

struct ServiceA {
    clientA: DataRetrieverForA,
}

struct ServiceB {
    clientB: DataRetrieverForB,
}

impl Service<Result<ADataStruct>> for ServiceA {
    fn get_values(&self, id: u64) -> Result<ADataStruct>;
    fn convert_data(&self, input: Result<ADataStruct>, output: &mut Vec<MyDataStruct>) -> Result<String>;
}

impl Service<BDataStruct> for ServiceB {
    fn get_values(&self, id: u64) -> BDataStruct;
    fn convert_data(&self, input: BDataStruct, output: &mut Vec<MyDataStruct>) -> Result<String>;
}

the self.get_values(id) uses the self.clientX to retrieve data and self.convert_data(input, &mut output) transforms data from ADataStruct and BDataStruct to MyDataStruct before saving it to my database.

The app will run using either ServiceA or ServiceB depending on command line input:

fn main() {
    // ...

    let service: Box<&(dyn Service<_>)> = match cli_service {
        Service::A => { Box::new(&ServiceA::new(...) }
        Service::B => { Box::new(&ServiceB::new(...) }
    };

    //...
}

I have tried many changes, mostly based on https://doc.rust-lang.org/book/ch10-02-traits.html and https://doc.rust-lang.org/book/ch17-02-trait-objects.html but I can't find an example that handles functions that use a generic type from trait definition. When I removed the generic parameter and fixed it to some common struct for testing, the code compiled and ran with no errors. So my guess is my mistake is with generic/trait usage.

The error I get with this code:


error[E0277]: the trait bound `ServiceB: Service<ADataStruct>` is not satisfied
  --> ori-runner\src\main.rs:40:37
   |
40 | ... { Box::new(&ServiceB::new(params)...
   |       -------- ^^^^^^^^^^^^^^^^^^^^^^ the trait `Service<ADataStruct>` is not implemented for `ServiceB`
   |       |
   |       required by a bound introduced by this call
   |
   = help: the following implementations were found:
             <ServiceB as Service<BDataStructure>>
   = note: required for the cast to the object type `dyn Service<ADataStruct>>`

What am I doing wrong? It is obvious the first match type is defining the '_' of the dyn Service variable, but I am out of ideas and google searches...

Thanks!

5
  • 1
    the problem is that your pointer should use the same type signature, Box<dyn Service<SOME_TYPE>>, SOME_TYPE is different for your implementations, so it is never gonna compile. Commented Mar 26, 2022 at 21:00
  • Yes, that is the problem. But if every service would return something different, which approach should I use then? I thought the _ was like a ? in java. If I can't use Generic typed traits to define method signatures, wouldn't they be useless? I feel I am missing something here... Commented Mar 26, 2022 at 21:39
  • if each service doesnt use the same data you cannot do it in this way. Commented Mar 26, 2022 at 21:45
  • Understood. Would you have a suggestion to give me? I would appreciate it if you could point me to some direction on how to implement this. Commented Mar 26, 2022 at 21:52
  • 1
    One possible solution is to put the rest of your main in a helper function/struct that is generic over a service (<T, S: Service<T>>) and then call the helper with the appropriate Service type based on the value of cli_service. Commented Mar 26, 2022 at 21:55

1 Answer 1

2

Since the types are different, one option would be to wrap them in an enum and have some method/s for computing whatever needed depending on the decision. The enum wrapper would abstract the services operations.

struct DataRetrieverForA {}
struct DataRetrieverForB {}

struct ADataStruct {}
struct BDataStruct {}
struct MyDataStruct {}

struct ServiceA {
    clientA: DataRetrieverForA,
}

struct ServiceB {
    clientB: DataRetrieverForB,
}

impl ServiceA {
    fn get_values(&self, id: u64) -> Result<ADataStruct, ()> {
        Ok(ADataStruct {})
    }
    fn convert_data(
        &self,
        input: Result<ADataStruct, ()>,
        output: &mut Vec<MyDataStruct>,
    ) -> Result<String, ()> {
        Ok("foo".into())
    }
}

impl ServiceB {
    fn get_values(&self, id: u64) -> BDataStruct {
        BDataStruct {}
    }
    fn convert_data(
        &self,
        input: BDataStruct,
        output: &mut Vec<MyDataStruct>,
    ) -> Result<String, ()> {
        Ok("bar".into())
    }
}

enum Services {
    A(ServiceA),
    B(ServiceB),
}

impl Services {
    fn a() -> Self {
        Self::A(ServiceA {
            clientA: DataRetrieverForA {},
        })
    }

    fn b() -> Self {
        Self::B(ServiceB {
            clientB: DataRetrieverForB {},
        })
    }

    fn compute(self) {
        todo!()
    }
}

fn main() {
    let arg = "a";

    let service = match arg {
        "a" => Services::a(),
        _ => Services::b(),
    };
    service.compute();
}

Playground

Sign up to request clarification or add additional context in comments.

2 Comments

I am trying this approach, thanks for your example. How would I get access to get_values and convert_data service methods from the enum's compute function?
you would have to match the enum: match self {...}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.