0
votes

How to parse some string to most appropriate type?

I know there is .parse::<>() method, but you need to specify type in advance like this:

fn main() {
    let val = String::from("54");
    assert_eq!(val.parse::<i32>().unwrap(), 54i32);
    
    let val = String::from("3.14159");
    assert_eq!(val.parse::<f32>().unwrap(), 3.14159f32);
    
    let val = String::from("Hello!");
    assert_eq!(val.parse::<String>().unwrap(), "Hello!".to_string());
}

But I need something like this:

fn main() {
    let val = String::from("54");
    assert_eq!(val.generic_parse().unwrap(), 54i32); // or 54i16 or 54 u32 or etc ...
    
    let val = String::from("3.14159");
    assert_eq!(val.generic_parse().unwrap(), 3.14159f32);
    
    let val = String::from("Hello!");
    assert_eq!(val.generic_parse().unwrap(), "Hello!".to_string());
}

Is there an appropriate crate for something like this? I don't want to re-invent the wheel for the umpteenth time.

EDIT

This is what I actually want to do:

struct MyStruct<T> {
    generic_val: T,
}

fn main() {
    let val = String::from("54");
    let b = MyStruct {generic_val: val.parse().unwrap()};
    
    let val = String::from("3.14159");
    let b = MyStruct {generic_val: val.parse().unwrap()};
}

Error:

error[E0282]: type annotations needed for `MyStruct<T>`
 --> src/main.rs:7:13
  |
7 |     let b = MyStruct {generic_val: val.parse().unwrap()};
  |         -   ^^^^^^^^ cannot infer type for type parameter `T` declared on the struct `MyStruct`
  |         |
  |         consider giving `b` the explicit type `MyStruct<T>`, where the type parameter `T` is specified
2
The problem here is assert_eq! not parse. The Rust compiler is capable of figuring out what type parameter parse should take without you telling it. The problem is you have to tell it, and all assert_eq! requires is that the types of its arguments be comparable with ==, not that they be the same. So is this exactly how you're using it, or if I suggest a fix that focuses on assert_eq! will you say that doesn't work in your real code and you need something different?trentcl
Ah, I just used assert_eq! as example. In real code, val from this example will be copied to a generic in a struct. let a = MyStruct {generic_val: val.copy(), ...etc or something like thatVitalijs
Ok, so the question becomes: why can't the compiler figure out the type argument to MyStruct? It's certainly possible to defer the choice of T until well after creating MyStruct<T>. Can you create a minimal reproducible example?trentcl
Added in main question.Vitalijs
I don't think what you want is possible. You have to tell Rust in one way or another what type you are expecting to parse; you can't just say "parse this as something and tell me what it is".Herohtar

2 Answers

3
votes

You need to base things on the right Enum type and implement FromStr for it. Like this.

#[derive(PartialEq, Debug)]
enum Val {
    Isize(isize),
    F64(f64),
}

impl core::str::FromStr for Val {
    type Err = & 'static str;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match (s.parse::<isize>(), s.parse::<f64>()) {
            (Ok(i),_) => Ok(Val::Isize(i)),
            (Err(_), Ok(f)) => Ok(Val::F64(f)),
            (Err(_), Err(_)) => Err("neither parser worked"),
        }
    }
}

 fn main() {
    assert_eq!("34".parse(), Ok(Val::Isize(34)));
    assert_eq!("12.3".parse(), Ok(Val::F64(12.3)));
    assert!("wrong".parse::<Val>().is_err());
}
3
votes

Rust is a statically typed language. This means that the compiler needs to know the type of variables at compile time. There are three ways things can go from there:

  • If your strings are known at compile-time, then you might as well replace them with literal values in your code (eg. "54"54).
  • If you have some other way of knowing at compile time what type a given string should parse to, then you can specify the appropriate type when parsing the string: let a = "54".parse::<i32>().unwrap()
  • If your strings are only known at run-time and you want to autodetect the type, then you need to use some kind of enumerated value that will store the type alongside the value in your program:
use std::str::FromStr;
enum Value {
    I32 (i32),
    F32 (f32),
    String (String),
}

impl Value {
    fn new (s: &str) -> Value {
        if let Ok (v) = s.parse::<i32>() {
            Value::I32 (v)
        } else if let Ok (v) = s.parse::<f32>() {
            Value::F32 (v)
        } else {
            Value::String (s.into())
        }
    }
}

That way, the rest of your code will have a way of knowing what type was detected and to adjust its processing accordingly.