| |
 | Posted by Jonathan M Davis | Permalink Reply |
|
Jonathan M Davis 
| On Friday, May 16, 2025 1:19:41 PM Mountain Daylight Time H. S. Teoh via Digitalmars-d-learn wrote:
> On Fri, May 16, 2025 at 07:04:24PM +0000, WhatMeWorry via Digitalmars-d-learn wrote: [...]
> > void main()
> > {
> > ubyte a;
> > a = a + 5; // onlineapp.d(11): Error: cannot implicitly convert expression `cast(int)a + 5` of type `int` to `ubyte`
> [...]
>
> Welcome to your first encounter with why I hate D's narrow integer promotion rules.
>
> Basically, `a + 5` gets promoted to int, and that's why it can't be assigned back to `a`. (Don't ask me why, that's just the way it is and Walter has refused and probably will continue to refuse to change it.)
It's because C (and I'm pretty sure the CPU as well) promotes integer types smaller than 32 bits to 32 bits to do arithmetic on them. So, in both C and D, the result is int. In general, C code is supposed to have the same semantics in D, or it's not supposed to be compile, and Walter is insistent that the behavior of arithmetic follows that (the one exception I'm aware of being that D defines what happens with overflow whereas C does not).
The difference between C and D is then that D doesn't allow narrowing conversions. This prevents certain classes of bugs, but when it's combined with the fact that smaller integer types get promoted to int when doing arithmetic, it can definitely become annoying if you want to do arithmetic with smaller integer types.
In some circumstances, VRP (value range propagation) will make it so that the D compiler is able to determine that the result will fit in the smaller integer type, so it will then do the implicit conversion, but because Walter tends to hate data flow analysis, it mostly just works in pretty basic situations.
> Ironically, this is OK:
>
> ```
> ubyte a;
> a += 5;
> ```
>
> But writing it as `a = a + 5;` is not.
This is because with +=, you don't get a result that's int. It's just mutating the ubyte, whereas a + 5 _does_ result in int, and you can't implicitly convert int to ubyte, because it's a narrowing conversion. It is kind of ugly that it works in one case but not the other, but it does make logical sense.
Given that Java and C# disallow narrowing conversions, I expect that they have similar restrictions, but I haven't done much with either of them any time recently, so I'm not sure what they do with smaller integer types and arithmetic. It's possible that they allow the narrowing conversion in some specific cases in order to be more user-friendly, but they may also treat it more or less the same way that D does. C and C++ avoid it because they allow narrowing conversions and thus invisibly truncate the result, which creates its own set of problems - but it does also mean that some code that doesn't truncate the values just works, whereas in languages that don't allow narrowing conversions, you typically have to cast, which of course can be annoying. But that's the price of avoiding invisible truncation with arithmetic. It's just that the fact that smaller integer types get converted to int for arithmetic makes it worse, though if they weren't, you'd be getting integer overflow with them pretty frequently, I expect.
- Jonathan M Davis
|