I have a trait that defines a state to be executed
pub trait State {
type Output;
fn execute(self) -> Result<Self::Output, Box<dyn std::error::Error>>;
}
This trait is meant to be called by a composable chain of states, like this
// this is called like this A.and_then(|_| B).and_then(|_| C).execute()
fn and_then<Next, F>(self, map_fn: F) -> AndThen<Self, Next, F>
where
Self: State + Sized,
Next: State,
F: FnOnce(Self::Output) -> Next,
{
AndThen {
previous: self,
map_fn,
_marker: Default::default(),
}
}
pub struct AndThen<Prev, Next, F> {
previous: Prev,
map_fn: F,
_marker: PhantomData<Next>,
}
impl<Prev, Next, F> State for AndThen<Prev, Next, F>
where
Prev: State,
Next: State,
F: FnOnce(Prev::Output) -> Next,
{
type Output = Next::Output;
fn execute(self) -> Result<Self::Output, Box<dyn std::error::Error>>
where
Self: Sized,
{
let previous_output = self.previous.execute()?;
let next_task = (self.map_fn)(previous_output);
next_task.execute()
}
}
The composition works fine when I have known states for the execution flow, the problem starts when I try to add dynamic states
// The branch function returns a Box<dyn State>, prev.branch(|_| if cond { Box::new(B) } else { Box::new(C) })
fn branch<Out, F>(self, branch_fn: F) -> Branch<Self, F>
where
Self: State + Sized,
F: FnOnce(Self::Output) -> Box<dyn State<Output = Out>>,
{
Branch {
previous: self,
branch_fn,
}
}
pub struct Branch<Prev, F> {
previous: Prev,
branch_fn: F,
}
impl<Prev, Out, F> State for Branch<Prev, F>
where
Prev: State,
F: FnOnce(Prev::Output) -> Box<dyn State<Output = Out>>,
{
type Output = Out;
fn execute(self) -> Result<Self::Output, Box<dyn std::error::Error>>
where
Self: Sized,
{
let previous_output = self.previous.execute()?;
let next_task = (self.branch_fn)(previous_output);
// This fails because it can't move out of Box<dyn State>
next_task.execute()
}
}
I can work around by introducing a new trait and using mem::transmute, but I guess it works by chance and it is probably UB
trait BoxedState {
type Output;
fn execute(self: Box<Self>) -> Result<Self::Output, Box<dyn std::error::Error>>;
}
impl<Prev, Out, F> State for Branch<Prev, F>
where
Prev: State,
F: FnOnce(Prev::Output) -> Box<dyn State<Output = Out>>,
{
type Output = Out;
fn execute(self) -> Result<Self::Output, Box<dyn std::error::Error>>
where
Self: Sized,
{
let previous_output = self.previous.execute()?;
let next_task = (self.branch_fn)(previous_output);
// Now the call works, with the unsafe transmute
let next_task: Box<dyn BoxedState<Output = Out>> =
unsafe { std::mem::transmute(next_task) };
next_task.execute()
}
}
Is there a way to implement that "move out of dyn trait" without changing the trait to be self: Box<Self>
?
Or is there a proper way to implement the trasmute/cast between the traits?
Here is a link with the full example in the Rust Playground
Thanks in advance