Doing very dynamic things in Rust

Hello,

I'm trying to make a website where you can convert between different file formats. I wanted to do it in Python, but decided I wanted to try to make it in Rust instead.
In Python, I wanted to represent each file as a class, make a list of functions representing the conversions, and iterate over the conversions to see which conversions are possible on a file (for example by using inspect.signature and isinstance).
In Rust, I have no idea how to do something like that. I wanted to use From and Into, but AFAIK there's not really a way to dynamically get the traits implemented for a type.
There might be a way to do it by representing each conversion as a separate trait with a method that returns an Option, and implement all of those traits for all the files. Apparently it's possible to use specialization to implement a default for it, but I can't get it to work, and that would be a suboptimal solution.

Is there any way to make such a system?

I don't really get why you would need to "dynamically get the traits implemented for a type". If you have:

then can't you just call those functions indirectly (i.e., via dynamic dispatch)?

If you share the concrete Python code, we could perhaps offer more useful help and "convert" it to idiomatic Rust. Based on this description only, what exactly you are after is unclear.

3 Likes

My first instinct would be to create an enum FileType with the file types your app wants to support and a struct File which represents an existing file. I'd then implement a method convert on File which will take the target FileType as argument and performs the conversion based on its own type and the given type. Here's a sketch:

enum FileType {
    PDF,
    Html,
    Markdown,
}

struct File {
    typ: FileType,
    location: std::path::PathBuf,
}

impl File {
    fn convert(&self, to: &FileType) -> Result<File, std::io::Error> {
        match (&self.typ, to) {
            (FileType::PDF, FileType::PDF) => { todo!() }
            (FileType::PDF, FileType::Html) => { todo!() }
            (FileType::PDF, FileType::Markdown) => { todo!() }
            _ => unimplemented!()
        }
    }
}
4 Likes

That's a great idea, thanks! Can you also do generic conversions with this? Like with From/Into I would do:

impl<T: Into<Image>, U: From<Image>> Into<U> for T

This is what I find so appealing about using types and traits for this, even though it doesn't work that well.

Note that this scheme will require writing n² conversions. If there are a lot of file types that you want to support, it is often helpful to define some kind of intermediate in-memory representation. That way, you can define separate import (file → intermediate) and export (intermediate → file) operations for each filetype. convert_a_to_b(...) then becomes export_b(import_a(...)) and you only need to write 2n separate implementations.

7 Likes

Yeah, that's the big problem. I guess it might be possible to do it using sub-enums:

enum FileType {
    Image(ImageType),
    // More file types here
}

enum ImageType {
    PNG,
    JPEG,
}

impl ImageType {
    fn into_image(&self, file: std::path::PathBuf) -> Image {
        match self {
            Self::PNG => Image::open_png(file),
            Self::JPEG => Image::open_jpeg(file),
        }
    }

    fn from_image(&self, image: Image) -> std::Path::PathBuf {
        match self {
            Self::PNG => Image.to_png(),
            Self::JPEG => Image.to_jpeg(),
        }
    }
}

struct File {
    typ: FileType,
    location: std::path::PathBuf,
}

impl File {
    fn convert(&self, to: &FileType) -> Result<File, std::io::Error> {
        match (&self.typ, to) {
            (FileType::Image(img_from_type), FileType::Image(img_to_type)) => {
                img_to_type.from_image(img_from_type.into_image(self.file))
            }
            _ => unimplemented!(),
        }
    }
}

Then if I want to convert a PDF to a JPEG, I would add a conversion between PDF to PNG, at the cost of the user needing to do two conversions.

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.