> I hope it’s uncontroversial that, like Rust, languages should not allow implicit casts between integer types at all.<p>I find this controversial. The unstated option #4 for addressing C's permissive implicit narrowing conversions is to simply disallow implicit narrowing conversions, but continue providing implicit integer conversions to types of greater-than-or-equal rank.<p>I suspect the reason you left out option #4 is an entirely different, self-imposed constraint in Rust:<p>> [Rust] defines From casts for integer types that will succeed on every platform. Since casting a 32-bit integer to a usize would fail on a 16-bit platform, I’m not ever allowed to compile — even on a 64-bit platform where such a cast would always succeed.<p>But therein lies the original questionable turn that Rust made wrt usize--there's no accounting for <i>future</i> platforms.<p>What makes C so portable is precisely the notion of C's integer ranking system and implicit conversions. By guaranteeing relative rank and permitting implicit widening conversions, most issues on most <i>future</i> architectures have been accommodated. It's not perfect, but the the value for your money is immense. The vast majority of issues like this go away.<p>And what do you lose by permitting implicit widening conversions? There are some potential correctness issues. For example, subexpressions computing bitmasks might not behave as expected when converted to a wider type in an outer expression. But this same problem exists in your proposed solutions and explicit conversions, generally. I would even consider implicit conversions safer because we can always add additional rules (optional or mandatory) that capture these cases (e.g. -Wwidth-dependent-shift-followed-by-implicit-widening), whereas explicit conversions usually have the effect of short-circuiting stronger type checking. (Unless you go the C++ route and add a panoply of conversion operators. But better hope you chose the right one!)<p>You lose the ability for a build to fail on target X because the conversion wouldn't work on target Y. But that requirement is fundamentally in conflict with accommodating <i>future</i> architectures, and incentives the type of explicit conversions that could hide or complicate future porting issues.<p>Regarding this footnote:<p>> [8] For reasons that are unclear to me, uintptr_t is an optional type in C99. However, I don’t know of any platforms which support C99 but don’t define it.<p>AFAIU, the reason is exactly because the committee foresaw that not all architectures could accommodate conversions between object pointers and integers. Relatedly, the C standard DOES NOT permit conversions between object pointers and function pointers, which also means the C standard DOES NOT permit conversions between function pointers and uintptr_t, even if uintptr_t is defined. Both capability systems and memory architectures already existed that couldn't accommodate the latter conversions. The value of function pointer/integer conversions was much less than object pointer/integer conversions, so they defined uintptr_t only for object pointers, and made uintptr_t optional.<p>Function pointer/object pointer conversions are widely supported by C compilers, but this is an extension that makes code non-conforming. See C11 J.5.7. Note that non-conforming does not mean undefined; it just means that such code is not C code as defined by the standard and beyond its purview. It creates a headache for POSIX, which defines a singular interface, dlsym, for acquiring <i>both</i> object and function references.