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

Reply via email to