Conversion from u64/usize to u128

Isn't it a bad implementation to have this (cf. rust/library/core/src/convert/num.rs at master · rust-lang/rust · GitHub)

// no possible bounds violation
macro_rules! impl_try_from_unbounded {
    ($source:ty => $($target:ty),+) => {$(
        #[stable(feature = "try_from", since = "1.34.0")]
        impl TryFrom<$source> for $target {
            type Error = TryFromIntError;

            /// Tries to create the target number type from a source
            /// number type. This returns an error if the source value
            /// is outside of the range of the target type.
            #[inline]
            fn try_from(value: $source) -> Result<Self, Self::Error> {
                Ok(value as Self)
            }
        }
    )*}
}

instead of a From implementation or at least have type Error = Infallible. Cuz (in my mind) there is no need to have a TryFrom implementation that will never give an Err and worst that do not state that the error case never happens

Note: this macro is only used for type conversion that cannot fails (ie: usize -> u64 on a 64bit arch for example)

In short: This is for portability.

Conversion from usize to u32 is infallible on 32-bit platforms, but fallible on 64-bit platforms.

Using TryFrom instead of From specifically for usize to u64 is a hedge against a potential future where pointer sizes grow beyond 64-bits on future platforms.

5 Likes

If you need conversions to/from usize/isize that are infallible when you know the target pointer width will be 64b, for example, you can use the cast crate. However, if you later compile your code for a target with a different pointer width, you will get compile errors when a conversion is no longer infallible or no longer fallible.

but this implementation is explicitly mentionned as NOT FALLIBLE, and is used behind cfgso even for portability this is not an argument as it is ONLY implemented for target where it cannot fail

for example

#[cfg(target_pointer_width = "64")]
mod ptr_try_from_impls {
    // ...
    impl_try_from_upper_bounded!(usize => u8, u16, u32); // X::from(usize) can fail
    impl_try_from_unbounded!(usize => u64, u128); // X::from(usize) can NEVER fail
    // ...
}

Because it is in fact not fallible. That is a given, and it is irrelevant.

The impl TryFrom<usize> for u64 on CHERI systems might (it is currently undecided) be fallible. See also: Policy for assumptions about the size of `usize` · Issue #1748 · rust-lang/rfcs · GitHub

Suppose you were given an impl From<usize> for u64 behind a cfg for 16-bit, 32-bit, and 64-bit platforms. Fine, it would work on those platforms. But it might not even be definable on a CHERI platform. The "doesn't compile on some platforms" problem is antithetical to the Rust standard library. It wants to provide, to the best of its ability, a unified interface for all platforms that the compiler supports, now and in the future. [1]

IMHO, awareness is the point. Everything else is coincidental. Developers may choose to unwrap or introduce their own newtype for "infallible conversions" if they disagree. But now they are aware that they are working against the grain. And importantly they are given an opportunity to consider the implications of their decisions. That's an affordance they don't get if the standard library lies.


  1. Notable exceptions are anything in core::arch and platform-specific extension traits. ↩︎

2 Likes

FWIW, there is also additional historical discussion on this topic in Tracking issue for platform-dependent-API TryFrom impls involving usize/isize · Issue #49415 · rust-lang/rust. Including acknowledgement of a general desire for infallible From impls, which are blocked on the absence of portability lints.

There is even more discussion in Document minimum size for usize and isize · Issue #48593 · rust-lang/rust.

And various links therein.

2 Likes

Thanks for all you explanation :smiley: love to learn more :slight_smile: I guess my last question is:

Why in that case (mentioned the CHERI platform) the implementation returns Ok(value as Self) ?

Isn't it missleading to imply that an Err can occur but use as conversion hunder the hood, which in release will just silently truncate the value if it does not fit ?

impl_try_from_unbounded!() is used for the TryFrom impls that can't overflow on the current platform. If you look further in the file you will see that all usages are gated behind #[cfg(target_pointer_width = "...")] that ensure they don't overflow and impl_try_from_upper_bounded!() is used for those that can overflow on the given target.

2 Likes