Runtime dynamic types

Not really sure what to call this, but I'm trying to write a tool for converting between differently formatted CSVs.What I want to do is something like this:

#[derive(Serialize, Deserialize)]
struct TypeA {
    // ...
}

impl From<TypeB> for TypeA {
    // ...
}

// ...

fn main() -> Result<()> {
    let matches = App::new("Formatter")
        .arg_from_usage("-i, --in-fmt=<IFMT> 'In format'")
        .arg_from_usage("-o, --out-fmt=<OFMT> 'Out format'")
        .arg_from_usage("<in-file>")
        .arg_from_usage("<out-file>")
        .get_matches();
    let out_file = File::create(matches.value_of("out-file").unwrap())?;
    let in_file = File::open(matches.value_of("in-file").unwrap())?;
    let mut csv_reader = csv::Reader::from_reader(in_file);
    let mut csv_writer = csv::Writer::from_writer(out_file);

    let mut source_iter = match matches.value_of("in-fmt").unwrap() {
        "typea" => csv_reader.deserialize::<TypeA>(),
        "typeb" => csv_reader.deserialize::<TypeB>(),
        // ...
        _ => bail!("Unsupported input format")
    };

    match matches.value_of("out-fmt").unwrap() {
        "typea" => source_iter.try_for_each(|row| csv_writer.serialize(TypeA::from(row?))),
        "typeb" => source_iter.try_for_each(|row| csv_writer.serialize(TypeB::from(row?))),
        // ...
        _ => bail!("Unsupported output format"),
    }?;

    Ok(())
}

This doesn't compile since the first match has arms with different return types. It seems I can't use a boxed trait because the only applicable trait I can think of, something like trait Formattable: Into<TypeB> + Into<TypeA> {} isn't object safe due to Into not being object safe and you can't fix the return type without collecting the iterator anyway which wont work for large files so I'm out of ideas.

You could use an enum, and implement any traits that you need to work across all variants on your enum.

It looks like you're using the csv crate. Are you familiar with the xsv program, built on the same crate by the same author? It has a fmt subcommand to "Reformat CSV data with different delimiters, record terminators or quoting rules."

Sorry, I'm not sure how using an enum helps? Can't serialize as a specific variant (csv_reader.deserialize::<TypeEnum::TypeA>() doesn't compile) and csv_reader.deserialize::<TypeA>().map_ok(TypeEnum::TypeA) still results in different return types for each match arm.

xsv looks neat, but isn't sufficient for my use case

That's strange, since both arms should be of type TypeEnum in this case. Could you share the example of code which fails to compile?

It looks like adding more functions to the iterator wraps the original type in another type. calling collect at the end would solve this particular issue but I don't want to load the entire file into a buffer before I start writing the output since that's ridiculous when you're working with something like a CSV.

`match` arms have incompatible types
expected type `itertools::adaptors::map::MapSpecialCase<csv::DeserializeRecordsIter<'_, _, TypeA>, itertools::adaptors::map::MapSpecialCaseFnOk<fn(TypeA) -> TypeEnum {TypeEnum::TypeA}>>`
 found struct `itertools::adaptors::map::MapSpecialCase<csv::DeserializeRecordsIter<'_, _, TypeB>, itertools::adaptors::map::MapSpecialCaseFnOk<fn(TypeB) -> TypeEnum {TypeEnum::TypeB}>>`

Even if that wasn't an issue, I dont think an enum is a good solution since it just results in a bunch of nested matches in the write section for every single combination of inputs and outputs. It makes more senseto just do something like this instead:

match (in_fmt, out_fmt) {
    ("typea", "typeb") => csv_reader
        .deserialize::<TypeA>()
        .try_for_each(|row| csv_writer.serialize(TypeB::from(row?))),
    ("typeb", "typea") => csv_reader
        .deserialize::<TypeB>()
        .try_for_each(|row| csv_writer.serialize(TypeA::from(row?))),
    // ...
    _ => bail!("Unsupported formats"),
}?;

This still seems like a huge mess if I add more than a few formats, though.

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.