Search code examples
genericsrusttraits

How to set a variable to implementations of generic typed Trait in rust?


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!


Solution

  • 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