hi there, i am new to rust and as my first task i am writing a script which digest different types of CSV tables. I got to a point it works nicely but there one issue with my code that is not elegant, here is a snippet which shows the problem.
trait RecordTrait {
fn identify(&self) -> &str;
}
struct Dog {
woof: String
}
impl RecordTrait for Dog {
fn identify(&self) -> &str {
self.woof.as_str()
}
}
struct Banana {
hmmm: String
}
impl RecordTrait for Banana {
fn identify(&self) -> &str {
self.hmmm.as_str()
}
}
enum RecordEnum {
A(Dog),
B(Banana),
}
impl RecordTrait for RecordEnum {
fn identify(&self) -> &str {
match *self {
RecordEnum::A(ref x) => x.identify(), // <------ is there a more elegant
RecordEnum::B(ref x) => x.identify(), // <------ way of doing that ?
}
}
}
fn main() {
let e = RecordEnum::A(Dog{woof: "its_a_dog".to_string()});
let r: &RecordTrait = &e;
println!("{:?}", r.identify());
}
the output is:
"its_a_dog"
is there a way to have a better impl RecordTrait for RecordEnum which doesn't have to match for every enum element as they all implement the same Treat?
Assuming you need the separate structs, trait, and the enum with tuple variants containing the struct then I don't think there's any other way. You can write a macro to remove some of the boilerplate, like this half-baked example:
enum RecordEnum {
A(Dog),
B(Banana),
C(Banana),
D(Banana),
E(Dog),
}
macro_rules! impl_record {
($($variant:tt)* ) => {
impl $crate::RecordTrait for $crate::RecordEnum {
fn identify(&self) -> &str {
match self {
$(
$crate::RecordEnum::$variant(x) => x.identify(),
)*
}
}
}
};
}
impl_record!(A B C D E);
thanks for the speedy reply if it helps i need the structs and the enum but i don't need the trait, i've added the trait as i thought it would do the trick here (but it didn't)
This is the natural choice when you statically know the type you're working with. Presumably, @vim-zz has the enum because he's parsing a row from a CSV and it's not known statically what type it is (but it's one of the enum variants).
well, it is just easier to have a separate struct for each CSV, i guess could have used enum with this types defined inside it. choosing between these two options i thought it be more clear to have a specific struct per CSV spec
Note that this doesn't allow you to control the visibility of the fields, so if that's a concern then this is a no go. It sounds like this is for your own internal stuff so likely not an issue.
So to summarize, if you don't intend to use the nested structs for their own distinct type-ness, then probably skip them.
well, it is a bit more complex as each one of theses types has associated methods which parse each CSV field differently - i hope it explain better why i meant it is clearer to use struct. in the sample here i have omitted this part for clarity.
Ok, that's clearer indeed. You could factor out parsing into helper types but I don't think they need to be methods and therefore hold any state themselves (besides maybe some buffer they're parsing out of). But I'm speculating here ...
yes i am using that, actually i couldn't use it in a straight forward way as it doesn't allow to 'choose' the CSV type in runtime, so i first using it to translate the data to JSON and then i use serde_json untagged enum to allow this.
i have to say, as a newbie here, that i have read that the rust community is helpful - and i am happy to experience it myself - thanks a lot @vitalyd and @dylan.dpc
Does a given CSV file contain a single type actually? I initially thought you had the different types intermixed but now need to double check that cause you mentioned something about ādifferent types of CSV tablesā in the initial post.
Ah, so you should be able to use csv's serde support and select the type to deserialize into. The example in the docs how the type is selected. The entry point code will still need to know statically which type to use, but that can be matched at a higher level. So something like (semi pseudocode):
match file_type {
"dogs" => read::<Dog>(...),
"bananas" => read::<Banana>(...),
}
fn read<T: Deserialize + Debug>(...) {
// using csv's doc example for most of this
let mut rdr = csv::Reader::from_reader(io::stdin());
for result in rdr.deserialize() {
// Notice that we need to provide a type hint for automatic
// deserialization.
let record: T = result?; <== type selected here
println!("{:?}", record);
}
}
coming from dynamic languages, my desire was not to rely on any external hints for choosing the right format, so at first i just thought of letting it fail and skip to the next type till it works, but eventually i got to this approach using serde_json untagged enums which really got me to where i wanted - almost perfect for me, beside the topic's issue of course
Rust (and the community), in general, will heavily steer you towards doing static type based coding. There're places where dynamicism is needed, and Rust has some support for it, but you'll get more out of the language (and likely have an easier time) if you try to use static information. In addition, performance will almost always be better when static types are involved. I realize you probably don't care too much about performance here since you're willing to go via json as an intermediate step .
You can certainly do the "try type X and fail to the next one" even statically. The RecordType enum is already static information about the types of records you expect. So you could do something like:
fn read<T: Deserialize + Debug>(...) -> Result<(), Error> {
// as before except we return a Result now
}
read::<Dog>(...).or_else(|_| read::<Banana>()).expect("unknown type in the file");
But I think I've offered too many different options at this point, so carry on .