Skip to content

ACP: Add TryFromIntError::kind method and IntErrorKind::NotAPowerOfTwo variant #746

@sorairolake

Description

@sorairolake

Proposal

Problem statement

TryFromIntError does not provide detailed information about the reason for an integral type conversion failure. For example, converting an i64 value to an i8 value will fail if the result is smaller than i8::MIN or larger than i8::MAX, but TryFromIntError cannot determine which is the cause of the failure.

Motivating examples or use cases

The nt-time crate I created provides methods to convert Unix time represented as a signed integer to a Windows file time. These methods perform integer type conversions internally, such as from i128 to u64, but if we can obtain the detailed cause of TryFromIntError, we can implement conversion from the TryFromIntError to this crate's error type. If this can be implemented, it will be useful to reduce the use of Result::map_err.

impl From<TryFromIntError> for FileTimeRangeError {
    fn from(error: TryFromIntError) -> Self {
        match error.kind() {
            IntErrorKind::PosOverflow => Self(FileTimeRangeErrorKind::Overflow),
            IntErrorKind::NegOverflow => Self(FileTimeRangeErrorKind::Negative),
            _ => unreachable!(),
        }
    }
}

Solution sketch

// core::num

impl TryFromIntError {
    /// Outputs the detailed cause of converting an integer failing.
    #[must_use]
    #[unstable(feature = "try_from_int_error_kind", issue = "none")]
    pub const fn kind(&self) -> &IntErrorKind {
        &self.0
    }
}

pub enum IntErrorKind {
    ...
    /// Value is not a power of two.
    #[unstable(feature = "try_from_int_error_kind", issue = "none")]
    NotAPowerOfTwo,
}

Since the Copy trait has been implemented for IntErrorKind since Rust 1.90.0, I think the return type of this method can be IntErrorKind instead of &IntErrorKind. However, I think it would be better to align it with ParseIntError::kind, so I plan to use &IntErrorKind as the return type.

NotAPowerOfTwo is a variant for impl TryFrom<usize> for Alignment. This returns TryFromIntError as an error if the given value is not a power of two. However, IntErrorKind does not have an appropriate variant to represents this. So I suggest adding a variant to represent this.

To maintain compatibility, the error message of TryFromIntError remains unchanged.

Currently, IntErrorKind is used to show the details of ParseIntError. I think this can also be used to show the details of TryFromIntError. If this is inappropriate, define TryFromIntErrorKind instead.

Definition of TryFromIntErrorKind
/// Enum to store the various types of errors that can cause converting an integer to fail.
///
/// # Example
///
/// ```
/// # fn main() {
/// if let Err(e) = i32::try_from(i64::MAX) {
///     println!("Failed conversion to i32: {:?}", e.kind());
/// }
/// # }
/// ```
#[unstable(feature = "try_from_int_error_kind", issue = "none")]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum TryFromIntErrorKind {
    /// Integer is too large to store in target integer type.
    #[unstable(feature = "try_from_int_error_kind", issue = "none")]
    PosOverflow,

    /// Integer is too small to store in target integer type.
    #[unstable(feature = "try_from_int_error_kind", issue = "none")]
    NegOverflow,

    /// Value was Zero
    ///
    /// This variant will be emitted when the converting integer has a value of zero, which
    /// would be illegal for non-zero types.
    #[unstable(feature = "try_from_int_error_kind", issue = "none")]
    Zero,

    /// Value is not a power of two.
    #[unstable(feature = "try_from_int_error_kind", issue = "none")]
    NotAPowerOfTwo,
}

Alternatives

Convert an integer to a string and parse it to get the detailed reason for the failure:

let s = (u8::MAX as u16 + 1).to_string();
assert_eq!(
    s.parse::<u8>().unwrap_err().kind(),
    &IntErrorKind::PosOverflow
);

However, this method is redundant because it requires converting an integer to a string once.

Also, when converting a negative integer of a signed integer type to an unsigned integer type (e.g., from i64::MIN to u32), the value of IntErrorKind is IntErrorKind::InvalidDigit, so it is difficult to understand that the conversion failed because the result was too small.

Links and related work

None.

What happens now?

This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

  • We think this problem seems worth solving, and the standard library might be the right place to solve it.
  • We think that this probably doesn't belong in the standard library.

Second, if there's a concrete solution:

  • We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
  • We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-libs-apiapi-change-proposalA proposal to add or alter unstable APIs in the standard libraries

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions