Rust: ParseIntError and ParseFloatError does not allow to introspect the cause

Created on 21 Feb 2015  路  21Comments  路  Source: rust-lang/rust

The only way to introspect reason of .parse failure is to compare to error messages returned by description or use the following code snippet:

let overflow = "1231123123123123".parse::<u32>().err().unwrap();
let underflow = "-1231123123123123".parse::<i32>().err().unwrap();
match "999999999999".parse::<u32>() {
    Ok(n) => println!("{}", n),
    Err(ref e) if *e == overflow => println!("Overflow"),
    Err(ref e) if *e == underflow => println!("Underflow"),
    _ => panic!("wow, no introspection, very convenience")
}
A-floating-point B-unstable C-tracking-issue I-nominated Libs-Tracked T-libs

Most helpful comment

If this is to be stabilized, I think that the terminology has to be improved: IntErrorKind uses Underflow to refer to negative overflow, but underflow usually means when the absolute value of a floating-point number is so small it becomes 0 (or a subnormal).

All 21 comments

Note that this was purposefully done to avoid adding extra #[stable] surface for now. It should be able to expose this information through an enum in the future, for example.

Triage: no change here.

@rust-lang/libs is the intention still to change this to an enum of some kind in the future?

Any reason to postpone this further?

@Seeker14491 I don't think it's postponed. I think someone just needs to propose an API?

Hey, we already have an enum IntErrorKind, just want to check if anything still need to be done to make it pub

https://github.com/rust-lang/rust/blob/fc02736d59252fe408dd6c2f7e2c4b6f229e4443/src/libcore/num/mod.rs#L2769-L2774

I just brushed up against this issue while demoing the guessing game example from the Rust Book - seemed like an easy thing to show off matching on the error kind, but turned out not so much!

I also wanted to match IntErrorKind::Empty in the number game to abort on EOF, turned out to be impossible.

@frontsideair you can just check if the string's empty before parsing, right?

I guess, but it was a fun exercise in pattern matching.

I see that checking for empty has come up in the context of the guessing game tutorial. Could somebody make a list of use cases from real code that needs to be able to distinguish between the following other ParseIntError cases?

  • invalid digit
  • number too large to fit in target type
  • number too small to fit in target type

In the event that empty is the only one we ever need to introspect for, I would prefer to close this and instead recommend to check == "" before parsing.

It can be convenient to distinguish too large/too small numbers when accepting configuration parameters that the code will later sanitize by clamping to a valid range. Specifically, they make it possible to perform saturation of the parsed value before it is passed on.

Example: the valid range of offsets is -10..+10, therefore the application stores it in an i8. When the value it outside of the -10..+10 range the app would like to warn the user and proceed with the nearest legitimate value.

Without the ability to discriminate if the number is too big/small, it is not possible to determine what is the nearest legitimate value.

@ranma42 do you think that use case would be solved by https://github.com/rust-lang/rfcs/issues/928?

use std::num::Saturating;

fn main() {
    let s = "-999";
    let n = match s.parse::<Saturating<i8>>()?.0 {
        std::i8::MIN ... -11 => { println!("saturating"); -10 }
        11 ... std::i8::MAX => { println!("saturating"); 10 }
        n => { println!("in range"); n }
    };
    println!("n={}", n);
}

Yes, that use case would be covered if Rust had the ability to parse a Saturating<i8> (and in a convenient way, too!)

The only way I found around this problem was to do something ugly like this:

match my_str.parse::<i32>() {
    Ok(x) => {my_int = x;},
    Err(e) => {
        if e.to_string() == "invalid digit found in string" {
            println!("Only positive integers are allowed!");
        }
        else if e.to_string() == "number too large to fit in target type" {
            println!("Number is too large!");
        }
        else if e.to_string() == "number too small to fit in target type" {
            println!("Number is too small!");
        }
        else if e.to_string() == "cannot parse integer from empty string" {
            println!("Number is empty!");
        }
    }
}

What is so annoying is that println!("{:?}", e) shows me ParseIntError { kind: InvalidDigit }, but kind is private so we can't access it. Either kind should be made public or a kind() function should be implemented to access to.

I'm doing custom type parsing (something like this: X+123789Y-3453), and I'd actually like to map the errors into my own custom error type. Yes, I could implement my own integer parsing, but that would defeat the purpose of _having_ a built-in integer parser.

My map would be something like this:

  • Empty -> MyInvalid (because the whole thing isn't empty, just one of the number portions)
  • Underflow/Overflow -> MyUnderflow/MyOverflow (although I'd be fine with just overflow, a la checked_)
  • Invalid -> MyInvalid

....except I can't do that at the moment without resorting to @amosbatto 's trick. It also makes testing very difficult, because I can't tell what error I got back (except by checking the string).

Hello everyone. My PR fixes this issue and has been merged.

Use the feature flag #![feature(int_error_matching)] in nightly to try it out.

If this is to be stabilized, I think that the terminology has to be improved: IntErrorKind uses Underflow to refer to negative overflow, but underflow usually means when the absolute value of a floating-point number is so small it becomes 0 (or a subnormal).

Maybe PosOverflow and NegOverflow?

This will also solve my use case described in #39381.

What is missing to make it stable?

I would find this useful for NonZeroUsize::from_str() to distinguish between "0" and an invalid number.

Was this page helpful?
0 / 5 - 0 ratings