I'm trying to develop a kind of batch system. Within that I'd like to use some kind of Process
struct, which owns all process related parts. The current implementation uses PhantomData
to enforce the type constraints:
pub struct Process<P: Producer<U>, T: Transformer<U, V>, C: Consumer<V>, U,V>
{
producer: P,
transformer: T,
consumer: C,
p1: PhantomData<U>,
p2: PhantomData<V>,
}
The idea is that type emitted by the Producer
will be used by the Transformer
(maybe to a different type) and consumed by the Consumer
. Therefore the types must match.
The Process
struct should own the items implementing the Producer
, Transformer
and Consumer
traits. I think that's why I need to use type parameters. Since I cannot use the the trait directly like
...
producer: Producer<U>,
...
because of the unknown size at compile time.
Is there a better way of doing this? I'm pretty new to Rust, so I might be thinking in the wrong direction.
The solution works, but it looks a bit odd with those PhantomData
fields. Maybe that is just what PhantomData
is used for?
Instead of type parameters, you want associated types:
trait Producer {
type Output;
fn produce(&self) -> Self::Output;
}
trait Transformer {
type Input;
type Output;
fn transform(&self, val: Self::Input) -> Self::Output;
}
trait Consumer {
type Input;
fn consume(&self, val: Self::Input);
}
struct Process<P, T, C>
where P: Producer,
T: Transformer<Input = P::Output>,
C: Consumer<Input = T::Output>
{
producer: P,
transformer: T,
consumer: C,
}
impl<P, T, C> Process<P, T, C>
where P: Producer,
T: Transformer<Input = P::Output>,
C: Consumer<Input = T::Output>
{
fn run(&self) {
let a = self.producer.produce();
let b = self.transformer.transform(a);
self.consumer.consume(b);
}
}
struct MakeNum;
impl Producer for MakeNum {
type Output = u8;
fn produce(&self) -> u8 { 41 }
}
struct AddOne;
impl Transformer for AddOne {
type Input = u8;
type Output = u8;
fn transform(&self, val: u8) -> u8 { val + 1 }
}
struct PrintNum;
impl Consumer for PrintNum {
type Input = u8;
fn consume(&self, val: u8) { println!("Value was {}", val) }
}
fn main() {
let process = Process {
producer: MakeNum,
transformer: AddOne,
consumer: PrintNum,
};
process.run();
}
Although I wouldn't normally add the where
clause on the struct
proper, I'd just have it on the impl
which would also have a new
method that ensures the constraints just as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With