Casts and some suggestions to avoid them

bearophile bearophileHUGS at lycos.com
Tue Apr 8 11:38:46 PDT 2014


In D (and other languages) casts are dangerous because often they 
punch holes in the type system, and they shut up the compiler, so 
nothing catches your mistakes. And even if you write correct code 
the first time, later you can change some types in your code and 
introduce some incongruity that casts will not complain about.

Phobos and D help avoid casts in several ways, like value range 
analysis, the new double(x) syntax, functions and templates like 
std.conv.signed and std.traits.Signed, the powerful converter 
to!, using "cast()" to convert to mutable without writing the 
type, using strongly pure functions to convert mutable results to 
immutable implicitly, using CTFE to initialize immutable data, 
using Unqual!, or using std.string.representation, using 
std.exception.assumeUnique, etc. D and Phobos are doing a lot to 
avoid the need to cast, but perhaps more can be done.

I've done a little statistics on about 208 casts in code I have 
written. The relative frequency of the various casts changes 
according to the kind of D code you write, if you do a lot of OOP 
with dynamic casts, or if you do lot of low-level programming (or 
lot of interfacing with C code), that often requires some casts.

Here beside the usage frequencies, I also show some examples of 
each kind, and some ideas to reduce the need to cast, usually 
with Phobos code.

- - - - - - - -

Of those casts about 73 casts are conversions from a floating 
point value to integral value, like:
cast(uint)(x * 1.75)
cast(int)sqrt(real(ns))

In some cases you can use the to! template instead of cast.

- - - - - - - -

About 20 casts are conversions from a floating point value 
returned by floor/round/ceil to integral, like:
cast(ubyte)round(x)
cast(int)floor(y)

At first looking at std.math I was a bit puzzled by those 
functions returning a floating point value. 99% of the times I 
need to cast their result to an integral value. But what type of 
integral type? So I think I'd like those functions (or similar 
functions) to accept a template type argument to specify what 
type I want the result:
round!ubyte(x)
floor!int(y)

- - - - - - - -

About 20 casts are for the return type of 
malloc/calloc/realloc/alloca, like:
cast(ubyte*)alloca(ubyte.sizeof * x);
cast(T*)malloc(typeof(T).sizeof * 10);

A set of 3 little wrappers around those functions in Phobos can 
remove those casts (this can't be done with alloca), they are 
safer than using the raw C functions:
cMalloc!T(n)
cCalloc!T(n)
cRealloc(ptr, n)

- - - - - - - -

About 14 are reinterpret casts, sometimes to see an uint as a 
sequence of ubytes, array casts, etc:
cast(ubyte*)&x;
cast(ubyte[4]*)&data;
cast(uint[])text.to!(dchar[])
cast(ubyte[3])[x % 256, y % 256, x % 256]

- - - - - - - -

About 8 casts are needed by the opposite of 
std.string.representation, so they replace a unrepresentation 
function.

See:
https://d.puremagic.com/issues/show_bug.cgi?id=10162

With such function in Phobos all or most of such casts are not 
needed.

- - - - - - - -

About 7 are caused by feqrel, that requires mutable arguments:
const double x, y;
feqrel(cast()x, cast()y)
I presume this is just a Phobos bug, so such casts can eventually 
be removed.

https://d.puremagic.com/issues/show_bug.cgi?id=6586

- - - - - - - -

About 6 casts are used to convert an array of enums to an array 
of the underlying type, like:

enum C : char { A='a', B='b' }
C[50] arr;
cast(char[])arr

Keeping 'arr' as an array of C is handy for safety or for other 
reasons, but perhaps you need to print arr compactly or you need 
the char[] for other reasons.

I think you can't use to! in this case.

- - - - - - - -

About 5 casts are used to convert the result of std.file.read to 
an usable array type (because in some cases readText is not the 
right function to use), like:
cast(char[])"data1.txt".read
cast(ubyte[])"data2.txt".read

The cast can be avoided with  similar function that accepts a 
template type (there are perhaps ways to this with already 
present Phobos functions, suggestions are welcome):
read!(char[])("data1.txt")

- - - - - - - -

About 4 casts are needed because the D compiler misses some 
"obvious" value range propagations, like:

void foo(immutable ulong x) {
     if (x <= uint.max)
         uint y = x;

     char['z' - 'a' + 1] arr;
     foreach (immutable i, ref c; arr)
         c = 'a' + i;
}


struct Foo {
     immutable char c;

     this(in int c_)
     in {
         assert(c_ >= '0' && c_ <= '9');
     } body {
         this.c = c_;
     }
}


See:
https://d.puremagic.com/issues/show_bug.cgi?id=9570
https://d.puremagic.com/issues/show_bug.cgi?id=10594
https://d.puremagic.com/issues/show_bug.cgi?id=10685
https://d.puremagic.com/issues/show_bug.cgi?id=12514

- - - - - - - -

About 4 casts are used by hex strings, like:

ubyte[] data = cast(ubyte[])x"00 11 22 33 AB";

I think hex strings should be implicitly castable to ubyte[], 
avoiding the need to a cast, or if you don't like implicit casts 
then I think they should be of type ubyte[], because in about 
100% of the cases I don't want a char[].

There are many cases of such useless cast in Phobos:
https://d.puremagic.com/issues/show_bug.cgi?id=10453

- - - - - - - -

In about 4 cases I have used a cast to take part of a number, 
like taking the lower 32 bits of a ulong, and so on.

In some cases you can remove such casts using a union (like a 
union of one ulong and a uint[2]).

- - - - - - - -

In 2 cases I have used cast because despite array concatenations 
generate a new array, if you concatenate two const/immutable 
arrays the result can't be a mutable (and I needed a mutable 
result):

void main() {
     const char[] a, b;
     char[] c = a ~ b;
     char d;
     char[] e = a ~ d;
}


This is an old issue:
https://d.puremagic.com/issues/show_bug.cgi?id=1654

- - - - - - - -

In 2 cases I have had to cast to convert an array length to type 
uint to allow the code compile on both a 32 and 64 bit system, to 
assign such length to some uint value.

- - - - - - - -

In 1 case I've had to use a dynamic cast on class instances. In 
theory in Phobos you can add specialized upcasts, downcasts, etc, 
that are more explicit and safer.

- - - - - - - -

I have also counted about 38 unsorted casts that don't easily fit 
in the precedent categories. They are so varied that it's not 
easy to find ways to avoid them.

Bye,
bearophile


More information about the Digitalmars-d mailing list