Generic transformation chain container

Hello there! I've got a quandary that I find a bit hard to wrap my head around.

Basically, I'm trying to build a collection of transformer functions that inherit a Transform trait, e.g

enum TargetContent {
    None,
    BinArray(Vec<u8>),
    String(String),
}

struct TargetDescriptor<Content> {
    pub content: Content,
}

type CompositeTargetDescriptor = TargetDescriptor<TargetContent>;

trait Transformer {
    fn transform(&self, transform_target: CompositeTargetDescriptor) -> Result<CompositeTargetDescriptor, Error>;
 }
 
struct TransformerContainer {
    pub transformers: Vec<Box<dyn Transformer>>,
}
 
impl TransformerContainer {
    pub fn transform(&self, mut transform_target: CompositeTargetDescriptor) -> Result<CompositeTargetDescriptor, Error> {
        for transformer in &self.transformers {
            transform_target = transformer.transform(transform_target)?;
        }
    }
}

This works as intended but I can't drop the feeling that there's a better way of doing this. Currently all the transformers that inherit the Transformer trait need to convert the CompositeTargetDescriptor into the generic TargetDescriptor<Content> which I do by using a series of From implementations e.g

impl Transformer for SomeTransformer {
   fn transform(&self, transform_target: CompositeTargetDescriptor) -> Result<CompositeTargetDescriptor, Error> {
      // Transform into the generic type here
      let mut target_descriptor: TargetDescriptor<String> = transform_target.into();
      // Do some transformations of the content here
      target_descriptor.content = "Scrappy".into_string();
      // Transform back into a composite descriptor
      Ok(target_descriptor.into())
   }
}

I'd much rather be able to implement a transformer struct that can specify a generic type so I don't have to do all the manual into's, something like:

impl Transformer for SomeTransformer {
   // TargetDescriptor implements the From<CompositeTargetDescriptor>
   fn transform(&self, target_descriptor: TargetDescriptor<String>) -> Result<Into<CompositeTargetDescriptor>, Error> {
      target_descriptor.content = "Scrappy".into_string();
      Ok(target_descriptor)
   }
}

This doesn't work well with having a transformer container as it requires the size to be known at compile-time, and this does not seem to work:

trait Transformer {
    fn transform(&self, transform_target: From<CompositeTargetDescriptor>) -> Into<CompositeTargetDescriptor, Error>;
 }
 
struct TransformerContainer {
    pub transformers: Vec<Box<dyn Transformer>>,
}

as I get the error

the trait `std::convert::From` cannot be made into an object

`std::convert::From` cannot be made into an object

note: the trait cannot be made into an object because it requires `Self: Sized`

So yeah, I'm not sure if there's any better way of implementing this, I'm happy with the current implementation as it seems to work fine, but would be glad to know of any other way of implementing this in a nicer way as I feel like I've exhausted my (very limited) knowledge in Rust, and probably programming in general.
Thanks in advance!

The error you're getting is because you are trying to use a trait (the From trait) as if it was a type. It isn't. It's a trait.

Anyway, I wonder why you're trying to do this. In what situation would you want a TransformerContainer?

Yeah I guess I can kind of wrap my head around that, I guess I just want to do something similar but am not really sure of what approach to use.

The basic idea is to be able to create some generic approach that could produce a system that transforms files through chaining. An example would be a static site builder (which is where I started with this whole idea) where you'd have a chain of transformers that would look something like this:

let transformers = TransformContainer {
   transformers: vec![
      FrontMatterTransformer::new(),
      MarkdownToHTMLTransformer::new(),
      MinifierTransformer::new()
   ]
};

let transformed_files;
for entry in files {
        let transformed = transformers.transform(entry);
        transformed_files.push(transformed);
}

I'm not sure how useful this data structure is though and would be happy to change the whole idea if there's any smarter way of implementing something similar.

I think you're making it more complicated than need be. Your trait doesn't have to be more than just this:

trait FileTransformer {
    fn transform(&mut self, contents: Vec<u8>) -> Vec<u8>;
}

There really is no reason to mess with From or Into traits here.

If you want to chain multiple transformers, it would be easy to implement a chain type, either like this:

// Can be nested for chains longer than two.
struct FileTransformerChain<A, B> {
    first: A,
    second: B,
}

impl<A, B> FileTransformer for FileTransformerChain<A, B>
where
    A: FileTransformer,
    B: FileTransformer,
{
    fn transform(&mut self, contents: Vec<u8>) -> Vec<u8> {
        self.second.transform(self.first.transform(contents))
    }
}

or like this:

struct TransformerVec {
    vec: Vec<Box<dyn FileTransformer>>,
}
impl FileTransformer for TransformerVec {
    fn transform(&mut self, contents: Vec<u8>) -> Vec<u8> {
        let mut result = contents;
        for transformer in self.vec.iter_mut() {
            result = transformer.transform(result);
        }
        result
    }
}

If any of the transformers have settings, make them fields in the transformer object.

1 Like

Ahh amazing, the first chaining example is exactly what I was looking for, thank you so much for the help!

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.