Function Generics - Noob needs help

Learning rust, some of the higher concepts still elude me. I have the following code:

fn get_time (xpath_item_tree: &XpathItemTree, xpath: &str) -> Result<Vec<NaiveDateTime>, Box<dyn Error>>  {

    let values_xpath = xpath::parse(xpath).expect("xpath is invalid");

    let values = values_xpath.apply(&xpath_item_tree)?;

    let mut vector: Vec<NaiveDateTime> = Vec::new();

    for element in values {

        let text = element.extract_as_node().extract_as_tree_node().text(&xpath_item_tree);

        vector.push(NaiveDateTime::parse_from_str(&text, "%Y/%m/%d %H:%M:%S").unwrap());
    }

    Ok(vector)
}

fn get_f64 (xpath_item_tree: &XpathItemTree, xpath: &str) -> Result<Vec<f64>, Box<dyn Error>>  {

    let values_xpath = xpath::parse(xpath).expect("xpath is invalid");

    let values = values_xpath.apply(&xpath_item_tree)?;

    let mut vector: Vec<f64> = Vec::new();

    for element in values {

        let text = element.extract_as_node().extract_as_tree_node().text(&xpath_item_tree);

        vector.push(text.parse::<f64>().unwrap());
    }

    Ok(vector)
}

pub(crate) fn get_string_data(id: i32, body: String) -> Result<AuxTimeData, Box<dyn Error>>  {
    println!("Parsing HTML for AUX data...");

    let document = html::parse(&body)?;
    let xpath_item_tree = xpath::XpathItemTree::from(&document);

    let time = get_time(&xpath_item_tree, "/html/body/div[2]/div/div[1]/div[1]/div/div[2]/div[2]/div/div[13]/table/tbody/tr/td[2]").unwrap();
    dbg!(&time);

    let temp = get_f64(&xpath_item_tree, "/html/body/div[2]/div/div[1]/div[1]/div/div[2]/div[2]/div/div[13]/table/tbody/tr/td[3]").unwrap();
    dbg!(&temp);

    let cond = get_f64(&xpath_item_tree, "/html/body/div[2]/div/div[1]/div[1]/div/div[2]/div[2]/div/div[13]/table/tbody/tr/td[4]").unwrap();
    dbg!(&cond);

    let press = get_f64(&xpath_item_tree, "/html/body/div[2]/div/div[1]/div[1]/div/div[2]/div[2]/div/div[13]/table/tbody/tr/td[5]").unwrap();
    dbg!(&press);

    Ok(AuxTimeData {id, time, cond, temp, press})

}

It works, but the duplicated code bugs me. I tried this - clearly wrong - approach but it is nowhere near compiling:

fn get_vals <T> (xpath_item_tree: &XpathItemTree, xpath: &str) -> Result<Vec<T>, Box<dyn Error>>  {

    let values_xpath = xpath::parse(xpath).expect("xpath is invalid");

    let values = values_xpath.apply(&xpath_item_tree)?;

    let mut vector: Vec<T> = Vec::new();
    
    // this is all wrong ...

    match T { // Cannot find value `T` in this scope
        NaiveDateTime => for element in values { // Variable `NaiveDateTime` is never used
            let text = element.extract_as_node().extract_as_tree_node().text(&xpath_item_tree);
            vector.push(NaiveDateTime::parse_from_str(&text, "%Y/%m/%d %H:%M:%S").unwrap());
        }
        f64 => for element in values { // Variable `f64` is never used
            let text = element.extract_as_node().extract_as_tree_node().text(&xpath_item_tree);
            vector.push(text.parse::<f64>().unwrap());
        }
    }

    Ok(vector)
}

Thinking I could then use something like:

let x = get_vals<NaiveDateTime>(...)

I would really appreciate a nudge in the right direction - how would a proper rust programmer approach this?

What you want is to generalize the f64 and NaiveDateTime interfaces into a single interface. Something like:

trait MyFromStr {
    fn parse(s: &str) -> Result<Self, Box<dyn Error>>;
}

impl MyFromStr for f64 {
    fn parse(s: &str) -> Result<Self, Box<dyn Error>> {
        Ok(s.parse::<f64>()?)
    }
}

impl MyFromStr for NaiveDateTime {
    fn parse(s: &str) -> Result<Self, Box<dyn Error>> {
        Ok(NaiveDateTime::parse_from_str(s, "%Y/%m/%d %H:%M:%S")?)
    }
}

Then you can use your MyFromStr as a trait bound on T, allowing you to call the <T as MyFromStr>::parse method on it:

fn get_vals<T: MyFromStr>(xpath_item_tree: &XpathItemTree, xpath: &str) -> Result<Vec<T>, Box<dyn Error>> {
    let values_xpath = xpath::parse(xpath).expect("xpath is invalid");

    let values = values_xpath.apply(&xpath_item_tree)?;

    let mut vector: Vec<T> = Vec::new();

    for element in values {
        vector.push(T::parse(element)?);
    }

    Ok(vector)
}
1 Like

Excellent. Thanks very much!

1 Like