Architectural portability as a first class artifact of the build and dependency systems


#1

I’ve recently found myself arguing that Rust should not try make architectural portability (notably size of architecture) too automatic, and some operations should fail to compile on some platforms that they weren’t built to support. Previous comments are here and here

Below I present both arguments about why this should be the case, as well as some compiler/cargo/community ways that can make a very beneficial situation out of that seemingly bad one.

Throughout, I will be using the scenario of code developed for a wider(64 or 32) bit system and being attempted to be compiled and run on 16 bit one. However, I believe that many of the same ideas can apply to other aspects of architectural (non)portability such as when inline asm is used, or endianness is different.

It is clear that widening is much safer (whether or not we get implicit widening) than narrowing. Right now to interoperate with int/uint types in the standard library (let alone doing any FFI), the expectation is that either the developer will use int/uint in their own code (consensus emerging that that is harmful) or they will be casting it to a specific type using “as”.

No matter how the whole implicit widening/polymophic indexing discussion pans out, I’m sure that any approach that does implicit narrowing will be rejected.

pub fn main() {
    let i =  99999i32;
    let j = i as i16;
    let k = i as i64;
    println!("{}",i);
    println!("{}",j);
    println!("{}",k);
}
99999
-31073
99999

One of thse is most definitely not like the other.

So on to some general proposals, while attempting to avoid any syntactical specifics

  1. Widening and narrowing should be handled differently. More specifically, there should be one syntactical way of doing a cast only between like-size types or types where the destination is larger than the source. There should be another way to do a cast that would allow narrowing in addition to supporting the safer same-size or wider casting semantics.
  2. An explicit narrowing cast should be either a warning or an error unless an annotation is explicitily opted into.
  3. A cast can be either narrowing or widening on different architectures. If the standard (non-narrowing) cast is used, this should be a platform specific compilation error when casting to or from int/uint on a target in suh a way that would result in a narrowing.
  4. Valid casting for the target platform should be a lint when it would be invalid on another platform.
  5. Crates (modules?) can specifically opt in (or out?) to compatibility with any target platforms, and the lints will only apply to dangerous narrowing that could occur on platforms that the crate or module is supposed to support.
  6. The platform compatibilty flags at the crate level can and should be used by crates.io to explicitly display which platforms each particular crate supports.

The key is #5. While it seems like a burden to add this explicit non-portability, my argument is that it’s already there, and that by making it explicit at the cargo and compiler levels, we will get a much better ecosystem of libraries, and that pull requests will rapidly address insufficient portability when it is the easy and correct thing to add to a given crate.