Hello,
I am having a kind of a hard time trying to solve what I think is a common paradigm.
What I would like?
use async_trait::async_trait;
enum WorkflowResult {
Completed,
InformationRequired(Value),
}
pub struct SomeParam {
key: String,
next_key: String,
}
#[async_trait()]
trait Step {
async fn execute(&self, param: &SomeParam) -> WorkflowResult;
}
struct FirstStep {}
#[async_trait()]
impl Step for FirstStep {
async fn execute(&self, param: &SomeParam) -> WorkflowResult {
// make some possible calls.await
println!("First step");
WorkflowResult::Completed
}
}
struct SecondStep {}
#[async_trait()]
impl Step for SecondStep {
async fn execute(&self, param: &SomeParam) -> WorkflowResult {
println!("Second step");
WorkflowResult::Completed
}
}
pub async fn execute_workflow(param: &SomeParam) {
let workflow: Vec<Box<Step>> = vec![Box::new(FirstStep {}), Box::new(SecondStep {})];
for s in workflow {
s.execute(¶m).await;
}
}
This code does not compile, because
the trait `Send` is not implemented for `dyn workflow::Step`
As far as I can understand, the Send trait is related to multithreading. However, what I have here is a vector of steps that should be executed one after each other (not in paralel). Further more, trying to replace a Box by an Arc (presumably thread safe) posed a very similar result.
In c++, I would just create classes which derive from a class with a pure virtual function and use the base pointer as the type of the collection anyways (destructors are also virtual, so no problems here). The sync trait with a Box works, as far as I can understand, the same. But when we put async together the problems start to arise. But I think this is so common that we should have it documented somewhere and I can assure you I have looked into so, so many places and have found nothing.
If I use the Vec with sync traits, things just work, but each step needs to call http code and the framework is Tokio anyways, so I would like to keep things as async as possible.
This is the very simplest workflow I would write, otherwize my code will become a mess. A more sophisticated one would include another function on the trait which takes a Step as a parameter and set for each Step its next step, so that the executor would need to call only execute on the first step, but if someone could help me to make even this simple workflow here work it would be great.
Thank you,
Marlon