I try to implement a system of transaction.
I want to have a lot of service that have a function to pass (I call that action) and if the function fail, I revert with a function (I call that compensate).
The transaction have multiple service that must all pass in order. If one fail, we revert all services with compensate function.
In a first time, I was thinking to add a service with the two functions (action and compensate).
Finally, I think about doing that with trait.
In my example, I have only one service that just log message, but Imagine the transaction must launch multiples services to success.
I have problem to understand how to pass the problem of size not know at compilation... and the generic type that must implement Service trait...
Maybe, there other way that are more adapted to this kind of problem ?
I would like to learn advanced rust with this example.
for the services field, you need to use Vec<Box<dyn Service<Data = T>>>. dyn Trait is a DST, which can only be accessed indirectly through some pointer types. for non-owning situations, you can use &dyn Trait or &mut dyn Trait. for owning cases like this example, you can use Box<dyn Trait> (or other smart pointer types like Rc, Arc etc).
then the add_service() function can either take a Box<dyn Service> as argument, or you can use a generic type and box it internally.
in code:
pub struct Transaction<T> {
id: u64,
services: Vec<Box<dyn Service<Data = T>>>, //<--- note the `Box`
}
impl<T> Transaction<T> {
// version 1: use `Box<dyn Service>` directly and let the caller box it
pub fn add_service_1(&mut self, service: Box<dyn Service<Data = T>>) -> &mut Self {
self.services.push(service);
self
}
// version 2: use generic type and box it internally
pub fn add_service_2<S>(&mut self, service: S) -> &mut Self
where
S: Service<Data = T> + 'static
{
self.services.push(Box::new(service));
self
}
}
As an inspiration you can look at how tower::Service trait is defined, and read tokio's blog post on Inventing the Service trait. The problem you described looks like a specialisation of tower::Service, so it might be a good reference point for you, when you are defining your own transaction stack.
Thank you for your answer. I will look all of that.
I was wondering that maybe my proposition have already a problem...
In fact T will be a String in my example, but if I have multiple services with Data is a different kind of type, the transaction can be created...
Imagine LogService with Data = String
and another service SecondService with Data = u32 or a struct type for example...
Finally, I do something that seems very more simple...
I do not use get_data and set_data because they are in the different structure if they need to share data.
By reading doc, I learn that Output type must be in trait with
trait my_trait {
type Output...
But when using a Data in parameter, we used a generic type like :
trait my_trait {
...
But I wonder how to have a Output that is the same that the parameter Data in the case we want to have a vec with a lot of element that implement the trait but for different type... because we don't care the type... we only call the function defined in the trait... ?