Proposal: Platform-dependent conversions

In writing platform-dependent code, I often find myself wanting infallible conversions such as u32 -> usize (infallible on 32-bit and 64-bit platforms). From and Into impls on the primitive types are only provided for conversions which are infallible on all platforms. How would folks feel about adding platform-dependent conversion traits that are the analogues of the From and Into traits, but implemented differently depending on the target platform?

Today, doing platform-dependent conversions like u32 -> usize usually entails a cast that just happens to be lossless (e.g., some_u32_value as usize), possibly combined with a comment explaining why it happens to be lossless. With these traits, we could have the much more robust usize::from(some_u32_value) and all of the attendant goodness that type safety brings.

How are you going to gate this so that the same code, ported to a platform where usize is u24, generates compile-time errors? It seems to me that platform-dependent code fractures the Rust ecosystem, so that code which works on some platforms misbehaves on other platforms. Of course you can already use assembly, which usually is architecture-dependent and often platform-dependent.

The idea would be something like:

// name to be bikeshedded
trait From<T> {
    fn from(t: T) -> Self;
}

#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
impl From<u32> for usize { ... }

// ...and so on for other types

User code would fail to build on platforms without these impls just as code which relies on the platform-specific parts of std such as std::os::unix does today if built on the wrong platform.

As for the portability concern, there will always exist some code which is explicitly designed to not be portable - sometimes entire crates, and sometimes just individual modules or functions within a larger, platform-agnostic crate - and it would be good if that code were more type safe.

If you use std::sys::unix, it's explicit that you're doing something target specific.

If you have a function that takes Into<usize> and you give it a u64 computed elsewhere, you might not even realize that you just wrote code that only works on 64-bit targets.

1 Like

In case it wasn't clear, my proposal is to introduce new traits, not re-use the existing ones. Maybe names like PlatformFrom or something would be better in order to be explicit?

Ah, you said analogues of the From and Into traits, I missed that. I do think they should be named distinctly, since the current traits are even in the prelude.

1 Like

For some relevant prior discussions, see Numeric .into() should not require everyone to support 16-bit and 128-bit usize.

Tangentially related, there have also been discussions around allowing some traits that operate on usize (like Index) to take other integer types, which would reduce the need for conversions.

1 Like

Thanks for the feedback, folks! I've posted a pre-RFC.

I'm definitely annoyed every time I write my multi-megabyte-large executables that process gigabytes of data in RAM, but have to do unchecked silently-truncating as casts, because Rust wants my code to compile without warnings on 16-bit platforms.

I thought fixes for usize were blocked on portability lints:

2 Likes