Struct with a field of multiple types

Hi everyone,

I am trying to write a struct with fields that can be of more than one type but not sure how to go about it. Please assist. I know I have to use something like enum but I'm not sure how to "link" that to the struct.

Here's how the struct looks like

struct FileRecord {
    col1: i32,
    col2: String,
    col3: f64, // this can either be a float or an integer
    col4 u32,
    col5: char, // this can be either a single char or multiple chars
    col6: char, // this can be either a single char or multiple chars
}

Thank you

Well, unless I'm missing something, shouldn't it suffice to define

enum Dynamic {
    Float(f64),
    Int(i64),
}

and then declare col3: Dynamic with this type?

My issue is now with using the struct to parse a file.

This is what I have now

use csv;
use std::path::Path;
use std::collections::HashMap;


#[derive(Debug, serde::Deserialize)]
pub enum ALLELE {
    Char(char),
    String(String),
}


#[derive(Debug, serde::Deserialize)]
pub enum POS {
    Float(f64),
    Int(i32),
}

#[derive(Debug, serde::Deserialize)]
pub struct BimRecord {
    chr: i32,
    snp: String,
    pos: POS,
    bp: u32,
    a1: ALLELE,
    a2: ALLELE,
}

#[derive(Debug, serde::Deserialize)]
pub enum BimHashValues {
    SNP(Vec<String>),
    ALLELES(Vec<ALLELE>),
}


fn read_bim<P>(chrom: i32, bim_path: &P) -> csv::Result<Vec<BimRecord>>
    where
        P: AsRef<Path>,
{
    println!("... parse bim file: {} ...", bim_path.as_ref().display());

    let records = csv::ReaderBuilder::new()
        .has_headers(false)
        .delimiter(b'\t')
        .from_path(bim_path.as_ref())?
        .deserialize()
        .filter(|record: &csv::Result<BimRecord>| {
            record.as_ref().map_or(true, |r| r.chr == chrom)
        })
        .collect::<csv::Result<Vec<BimRecord>>>()?;

    Ok(records)
}


pub fn bim_hash<P>(chrom: i32, bim_path: P) -> HashMap<String, BimHashValues>
    where
        P: AsRef<Path>,
{
    let bim = read_bim(chrom, &bim_path);
    let cont = bim.as_ref().unwrap().len();

    let mut snp_vec = Vec::with_capacity(cont);
    let mut a1_vec = Vec::with_capacity(cont);
    let mut a2_vec = Vec::with_capacity(cont);

    for rec in bim.unwrap(){
        snp_vec.push(rec.snp);
        a1_vec.push(rec.a1);
        a2_vec.push(rec.a2);
    }

    assert_eq!(snp_vec.len(), cont);
    assert_eq!(a1_vec.len(), cont);
    assert_eq!(a2_vec.len(), cont);

    let bim_hash = HashMap::from([
        ("SNP".to_string(), BimHashValues::SNP(snp_vec)),
        ("A1".to_string(), BimHashValues::ALLELES(a1_vec)),
        ("A2".to_string(), BimHashValues::ALLELES(a2_vec)),
    ]);

    println!("... {} SNPs on chromosome {} read from {} ...", cont, chrom, bim_path.as_ref().display());

    bim_hash
}

but when I try parsing a file, I get the following error

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error(Deserialize { pos: Some(Position { byte: 0, line: 1, record: 0 }), err: DeserializeError { field: None, kind: Message("unknown variant `0.0`, expected `Float` or `Int`") } })', src/io/bim.rs:61:29
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Just based on the error and without seeing the file being parsed it appears as though the error may that the input lacks a label for which variant should hold the 0.0 value.
It is attempting to parse 0.0 as if it was the name of a variant of your enum. Here's a link to the serde docs showing how you would add a tag so that it is parsed correctly.
https://serde.rs/enum-representations.html#enum-representations

1 Like

With these changes:

 #[derive(Debug, serde::Deserialize)]
+#[serde(untagged)]
 pub enum ALLELE {
     Char(char),
     String(String),
 }

 #[derive(Debug, serde::Deserialize)]
+#[serde(untagged)]
 pub enum POS {
-    Float(f64),
-    Int(i32),
+    Int(i32),
+    Float(f64),
 }

Note that I changed the order of POS variants because "123" will parse as a float just fine, but "123.456" won't parse as an integer. (Similar considerations apply to ALLELE but the order was already okay.)

3 Likes

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.