Search code examples
parsingrust

Rust FromStr where the Type to parse into has Generic field and you won't know the type until after you attempt to parse


Lets assume you have a concrete type (that you created/own) you want to parse into but the type has a generic field, in this example, we'll say the possible types are only u8, u16 & u32 and let's just say that anything else is either incorrect or too big.

Assume the following:

  • I've simplified the code from what I'm actually using
  • I can't just convert to same type they have to be u8, u16 or u32 based on size (roughly simulating what I'm actually wanting
  • I'm showing it this way because the actual code pulls in way too much, this is the Minimal Code Example
  • I've tried an example doing impl FromStr for Test<u8> {} etc for each type but then the compiler complains (Test requires generics) and then complains about conflicting implementation when I then add impl<T> FromStr for Test<T> {} which makes sense since rust doesn't have specialisation yet
  • Actual code doesn't use u8, u16 or u32, I'm trying to make my example code more widely applicable

I have the following code:

use std::str::FromStr;

#[derive(Debug)]
struct Test<T = u8> {
    field: T
}

impl<T> FromStr for Test<T> {
    type Err = std::num::ParseIntError;
    
    fn from_str(buff: &str) -> Result<Test<T>, Self::Err> {
        let attempt: Result<u8, Self::Err> = buff.parse();
        if let Ok(field) = attempt { return Ok(Test::<u8> { field }) };
        
        let attempt: Result<u16, Self::Err> = buff.parse();
        if let Ok(field) = attempt { return Ok(Test::<u16> { field }) };
        
        let attempt: Result<u32, Self::Err> = buff.parse();
        
        match attempt {
            Ok(field) => Ok(Test::<u32> { field }),
            Err(err) => Err(err)
        }
    } 
}



fn main() -> Result<(), std::num::ParseIntError> {
    let u_8 = "128";
    let u_16 = "475";
    let u_32 = "70000";
    
    let test_8: Test = u_8.parse()?;
    let test_16: Test = u_16.parse()?;
    let test_32: Test = u_32.parse()?;
    
    println!("t8: {test_8:?}");
    println!("t16: {test_16:?}");
    println!("t32: {test_32:?}");
    
    Ok(())
 
}

I get a ton of errors like:

= note: expected struct `Test<T>`
               found struct `Test<u16>`

which makes sense.

How do I make this work?

Bonus points

Why does Rust require me to do Test::<u8>, since I've specified the type above each return line why can't Rust just infer the T in Test<T>?

Is it because of Result<Self, Self::Err> and Self is Test<T>?


Solution

  • Suppose we have a function which parses your type. Either it will be generic, and you need to decide upon a variant when you call the function, or it will have a single return type. Either way, when you call this function it will have a single unambiguous return type.

    Now there are two ways to make this single type be a front for different possibilities: it could be an enum listing a pre-determined number of possibilities, or it could be something that impl's a specific trait (like Box<dyn Display>), for an extensible number of possibilities. Nothing else is possible.