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