`TryFromIntError` should include the original value

AFAICT, TryFromIntError currently only outputs a generic "out of range integral type conversion attempted" message (https://doc.rust-lang.org/src/core/num/error.rs.html#6-46), but it would often be useful to know what the original value was, to aid in debugging.

Example use cases:

  • i* -> u*: negative values represent special values, and knowing which one was there would be very useful for debugging
  • iX -> *Y (X > Y): distinguishing positive vs negative overflow
  • (any): identifying where the value could have come from

Is there any reason the error messages for fallible integer conversions currently don't include the original value, or any other reasons not to include it?

Related:

1 Like

I know that in some languages there seems to be a de-facto rule that one doesn't reflect the "bad" input in the error message, perhaps to avoid leaking internal information. Admittedly, though, I can't really see how that would be a problem with integers -- it's more obviously an issue with parsing or file names and such. But I don't know what the std rule is, or should be.

More practically, I guess, it containing the original value would mean that it would need to be a generic, which seems like it's probably not a change that's possible without breaking code, since code today can rely on it being exactly a TryFromIntError, not a TryFromIntError<u128>. That could be worked around for built-in types, but not if part of the goal is for something like num::BigInteger to be able to use the same type.

Instead of the original value, would it be helpful to you to have it become a #[non_exhaustive] enum with the details? (Like negative-to-unsigned, and the other categories you mentioned?) That seems somewhat more feasible, and would still let Result<isize, TryFromIntError> continue to get optimized scalarpair handling in ABIs...

2 Likes

Hmm... My current use case is the first one, about having special values.

What I can do is just have a function or extension method that Copys the original value, attempts the conversion, and combines the existing error value with the original input value on failure.

FWIW, num appears to have a custom generic TryFromBigIntError type that stores the original value (when available, i.e. not passed as a reference).

I actually think this is an antipattern, and "bad" input should always be reflected in the error message (with extremely rare exceptions for data that is known in advance to be secret, passwords and such). I argued a couple years ago that io::Error should capture any file names passed to the failing system call, for instance.

5 Likes

I'm under the impression (possibly false) that the main reason io::Error doesn't capture file names is that it would require allocation, which fortunately isn't a concern with integral types. I'd be strongly in favour of capturing the bad value and including it in TryFromIntError (as well as the one to io::Error, but I see that that one has more trade-offs).

I also have a crate which allows deriving TryFrom<{i,u}*> for enums; we currently have our own TryFromPrimitiveError type and it would be great to be able to use one from std instead. (In fact, this custom error type is the only reason this crate needs separate num_enum and num_enum_derive crates - if we could use a type from std we would only need one crate).