Deprecate implicit conversion between signed and unsigned integers

Walter Bright newshound2 at digitalmars.com
Mon Feb 17 09:01:45 UTC 2025


On 2/13/2025 4:00 PM, Quirin Schroll wrote:
> Signed and unsigned multiplication, division and 
> modulo are completely different operations.

Signed and unsigned multiplication produce the exact same bit pattern result. 
Division and modulo are indeed different.


> None of those are a bad choice; tradeoffs everywhere.

It's always tradeoffs.


>> 4. What happens with `p[i]`? If `p` is the beginning of a memory object, we 
>> want `i` to be unsigned. If `p` points to the middle, we want `i` to be 
>> signed. What should be the type of `p - q`? signed or unsigned?
> 
> Two questions, two answers.
> 
>> What happens with `p[i]`?
> 
> That’s a vague question. If `p` is a slice, range error if `i` is signed and 
> negative. If `p` is a pointer, it’s `*(p + i)` and if `i` is signed and 
> negative, so be it. `typeof(p + i)` is `typeof(p)`, so there shouldn’t be a 
> problem.

Sorry, I meant `p` as a pointer. I use `a` as an array (or slice). A pointer can 
move forward or backwards, so the index is signed. A slice cannot back up, so 
the index is unsigned. A slice can be converted to a pointer. So then what, is 
the index signed or unsigned? There's no answer for that.


>> What should be the type of `p - q`? signed or unsigned?
> 
> Signed.

That doesn't work if the array is bigger than the int range, or happens to 
straddle `int.max`. (The garbage collector can run into this.)


> While it would be annoying for sure, it does make sense to use a function for 
> pointer subtraction when one assumes the difference to be positive: 
> `unsignedDifference(p, q)` It would assert that the result is in fact positive 
> or zero and return a `size_t`. The cool thing about it is that if you expect an 
> unsigned result and happen to be wrong, you’ll find out quicker than otherwise.

I'm sorry, all these extra baggage and rules about signed and unsigned makes it 
harder to use, not easier.


> As I see it, 2’s complement for both signed and unsigned arithmetic is a 
> straightforward choice D made to keep `@safe` useful.

D's type system preceded @safe by many years :-/


> If D made any of them UB, 
> it would exclude part of basic arithmetic from `@safe` because `@safe` bans 
> every operation that *can* introduce UB.

@safe only bans memory corruption. 2's complement arithmetic is not UB.


> It’s essentially why pointer arithmetic 
> is banned in `@safe`, since `++p` might push `p` outside an array, which is UB. 
> D offers slices as a safe (because checked) alternative to pointers.

`--p` and `++p` are always unsafe whether the implicit conversions are there or not.


>> 6. Casts are a blunt instrument that impair readability and can cause 
>> unexpected behavior when changing a type in a refactoring. High quality code 
>> avoids the use of explicit casts as much as possible.
> 
> In my experience, when signed and unsigned are mixed, it points to a design issue.
> I had this experience a couple of times working on an older C++ codebase.

Hence my suggestions.

I look at it this way. D is a systems programming language. A requirement for 
being successful at it is understanding 2's complement arithmetic, including 
what wraparound is.

It's not that dissimilar to the requirement of some understanding of how 
floating point code works and its limitations, otherwise grief will be your 
inevitable companion.

Also that a bool is a one bit integer arithmetic type.

I know there are languages that attempt to hide all this stuff, but D isn't one 
of them.


More information about the dip.ideas mailing list