November 10, 2013
On Sunday, November 10, 2013 12:10:23 Joseph Rushton Wakeling wrote:
> One of the challenges when working with unsigned types is that automatic wraparound and implicit conversion can combine to unpleasant effect.
> 
> Consider e.g.:
> 
>      void foo(ulong n)
>      {
>          writeln(n);
>      }
> 
>      void main()
>      {
>          foo(-3);
>      }
> 
> ... which will output: 18446744073709551613 (or, ulong.max + 1 - 3).
> 
> Is there a recommended way to handle this kind of potential wraparound where it is absolutely unacceptable?  I've considered the following trick:
> 
>      void bar(T : ulong)(T n)
>      {
>          static if (isSigned!T)
>          {
>              enforce(n >= 0);    // or assert, depending on your priorities
>          }
>          writeln(n);
>      }
> 
> ... but it would be nice if there was some kind of syntax sugar in place that would avoid such a verbose solution.

If you wanted to do that, you could simply do

void bar(T)(T n)
    if(isUnsigned!T)
{
    ...
}

and make it so that only unsigned types (and therefore only positive values) are accepted without casting.

> I know that there have been requests for runtime overflow detection that is on by default (bearophile?), but it could be good to have some simple way to indicate "really, no overflow" even where by default it's not provided.
> 
> (Motivation: suppose that you have some kind of function that takes a size_t and uses that to determine an allocation.  If a negative number gets passed by accident, the function will thus try to allocate 2^64 - n elements, and your computer will have a very happy time...:-)

Honestly, I wouldn't worry about it. It's a good idea to avoid unsigned types when you don't need them in order to avoid problems like this (though size_t is one case, where you have to deal with an unsigned type), but in my experience, overflow problems like this are rare, and testing usually finds problems like this in the rare cases that they do happen. I think that the only reason to really be concerned about it is if you feel that you need to be paranoid about it for some reason.

But if you really need to protect against it, I think that the only way to really do it cleanly is to create your own int struct type which protects you - similar to a NonNullable struct but for protecting against integer overflow. And I really think that the situation is very similar to that of nullable references/pointers. You _can_ run into problems with them, but it's rarely a problem unless you do a lot with null and aren't careful about it, and if you're paranoid about it, you use NonNullable. It's just that in this case, instead of worrying about null, you're worrying about integer overflow. Ultimately, I think that it's about the same thing and the the solutions for the two are about the same.

- Jonathan M Davis