Implementing TryFrom/TryInto trait (instead of From/Into)

Viewed 1339

I tried to "overload" a function using the From trait (so that it can accept a struct and a string):

pub struct Measurement {
    pub value: i16,
    pub unit: char,
}

impl From<&str> for Measurement {
    fn from(s: &str) -> Measurement {
        let value = s[0..s.len() - 1].parse::<i16>().unwrap();
        let unit = s.chars().last().unwrap();

        return Measurement { value, unit };
    }
}

pub fn print_measurement<T: Into<Measurement>>(value: T) {
    let m = value.into();
    println!("Measurement is {}{}", m.value, m.unit);
}

fn main() {
    print_measurement("40m");
    print_measurement(Measurement{value: 23, unit: 'g'});
}

As per Playground, this works as expected. However, since parsing of the string can fail, I wanted to use try_into(), instead of into(). So:

use std::convert::TryFrom;

#[derive(Debug)]
pub struct Measurement {
    pub value: i16,
    pub unit: char,
}

impl TryFrom<&str> for Measurement {
    type Error = String;

    fn try_from(s: &str) -> Result<Measurement, String> {
        let value = s[0..s.len() - 1].parse::<i16>();
        let unit = s.chars().last();
        match (value, unit) {
            (Ok(v), Some(u)) => Ok(Measurement { value: v, unit: u }),
            _ => Err("Invalid value or unit".to_string()),
        }
    }
}

pub fn try_print_measurement<T: TryInto<Measurement>>(value: T) {
    let m = value.try_into();
    match m {
        Ok(m) => println!("Measurement is {}{}", m.value, m.unit),
        Err(e) => println!("Error when parsing: {:?}", e),
    }
}

fn main() {
    try_print_measurement("4_0m"); // <-- this line should fail to parse
    try_print_measurement(Measurement{value: 23, unit: 'g'});
}

Questions:

  1. Unfortunately, the above fails with error[E0277]: '<T as TryInto<Measurement>>::Error' doesn't implement 'Debug'. Why isn't the error type equal to String as specified, but rather <T as TryInto<Measurement>>::Error? And what does this error type mean?
  2. Instead of let m = value.try_into() I tried let m = Measurement::try_from(value). But this fails with error[E0277]: the trait bound 'Measurement: From<T>' is not satisfied, which seems odd as I call try_from (and not from). Why is that?
  3. How would one correctly implement the TryFrom trait, so that the parsing errors can be dealt with as outlined?
  4. Why do we need to bring TryFrom into scope via use std::convert::TryFrom, whereas the From trait does not require this?
1 Answers
  1. associated type are still generic, why every implementation of TryInto<Measurement> would use String for TryFrom::Error ? Follow compiler hint. You could have an implementation for impl TryFrom<i32> for Measurement or whatever that use different associated type.
    pub fn try_print_measurement<T>(value: T)
    where
        T: TryInto<Measurement>,
        <T as TryInto<Measurement>>::Error: std::fmt::Debug,
    {
        let m = value.try_into();
        match m {
            Ok(m) => println!("Measurement is {}{}", m.value, m.unit),
            Err(e) => println!("Error when parsing: {:?}", e),
        }
    }
    
  2. cause your bound is TryInto<Measurement> not TryFrom<&str>. TryInto have blanked implementation when T implement TryFrom, not the opposite.
  3. you did
  4. cause Rust 2018 didn't include it in prelude, Rust 2021 does
Related