Last week I was writing some C code, and as is often the case in C, I started getting frustrated with the language and thinking about how it could be better. This time my thoughts were on casting.
The problem started when I encountered some code where a function needed to be passed a pointer to a
const value, but the data available was in the form
const volatile. Declaring a variable as
const volatile simply means that it it can change (volatile), but you cannot change it (const). It is useful for embedded microcontrollers, where there are cases where values stored in program flash ROM (const) that can actually be changed and so shouldn’t really be taken as “never changing”, but instead should be reread from program flash every time it is accessed.
But that is beside the point. The point is that I have to cast something like this:
const MyAwesomeType* myAwsomeVariable = (const MyAwesomeType*)myOtherVariable; myFunction(myAwsomeVariable);
One of my coworkers would never have used const for the function, and would just write it like this:
Certainly the latter is more readable and bit more compact, which is valuable. On the other hand, the former is more explicit and more type safe, which is also valuable. In this example I also tried to show a difference in naming convention – I prefer more explicit names, which makes lines of code harder to read, but individual variables easier to understand, while my coworker prefers shorter variable names which make the syntax of a line more readable. There is value in both approaches and the choice is purely a choice of style and philosophy, but I’m sure you can see there’s a similarity in the reasoning behind the choices of variable names and more explicit types.
Initially I was debating about which approach is better – to be more explicit or more compact – but then my thoughts turned to “why do I have to cast at all?”. I would say there are generally two reasons to explicitly cast:
- Either I know the type beyond what the compiler knows and therefore need to persuade it that the type is correct,
- Or I intend to convert the value to a type that it isn’t currently
These are really very different things, and I’m only really going to talk about the first case. In the latter case of converting the type, the type-casting syntax is really just a syntactic convenience for what could have been done with intrinsic functions.
Casting, in the latter case, is used to persuade the compiler to treat some data as a type that the compiler doesn’t otherwise think the data is. In the particular example of casting
volatile const to
const, we are persuading the compiler that for the purposes of
myAwsomeVariable, we don’t need to think about the data as
volatile. Of course this is a complete lie! Either the data is
volatile or it isn’t. If it isn’t volatile, then why was it declared that way? And if it is volatile, then why is it legal to say it isn’t? The code could introduce a subtle bug: the developer of
myFunction assumes that the data doesn’t change, while clearly there is some good reason that we need to be able to change it. In short:
There is a conflict of expectations between the caller and callee, for which the compiler nicely produces a type error, but which we suppress with a type cast and thus introduce a subtle bug
When I say “introduces a subtle bug”, I’m not being strictly truthful. There is no bug introduced in the code, because I’ve seen the functions myself and I know that the value won’t change for the duration of the function call. But this does emphasize my point: I needed to look at the code on both sides of the function call to know that it’s safe. This is bad. I need to know that
myFunction doesn’t cache the value across multiple calls (thinking it’s constant and persistent), and I need to know that the caller doesn’t change the value during the call (a threading race-condition), or during repeated calls (if myFunction also expects the value to be persistent across a longer time). The cast introduces one more piece of information that needs to be manually tracked throughout the program, coupling one part to another. In an ideal world, however, the function declaration should be enough to define the contract between the caller and the callee, when it comes to type consistency. Casting is an explicit breach of this contract, and so should never be allowed by the referee (the compiler).
Note that this situation isn’t unique to
volatile, of course. The same thing happens in C all the time with other type casts. It happens where we know the type of something that the compiler doesn’t, or we want to keep track of the “type” of something ourselves and not give it to the compiler because it’s too stupid to let us do what we want with it.
Another simple example might be a C container type, for which one implementation would be for it to contain
void*-typed items because C doesn’t have templates or generics. Once again we know more about the data than the compiler: we know what type of objects the container holds. And once again we have to perform casts to persuade the compiler of what we know. And once again, different parts of the program are coupled together by our special knowledge. That is, we have to keep track in our minds (or in the comments), so that the part that puts items into the container only puts in items that the other part of the program can take out. And once again this opens up a huge gaping whole for bugs to creep in.
What I’m saying is this: casting is not feature of the language, it’s a code-smell that says that either your code is badly designed, or that the language is conceding defeat and needs to give you a kludge because its type system has failed you.
I think the obvious solution is add more power to the type system. In the end, the compiler should know almost as much as we do about the code, so that if a cast is actually valid then it isn’t necessary. That is, if it’s valid to cast from
volatile const to
const, then the compiler already knew that it wasn’t volatile at that point of the program so we don’t need to cast it.
This is what happens with generics in C# – if we read an item from a
List<int>, then the compiler already knows that it’s valid to cast the item to
int, so a cast becomes redundant. That is, the item already is an
int, so there’s no point in casting it. Indeed in C# I find that I never need casts (except for integer conversions which aren’t the same thing). When I find myself needing a cast, its generally because I’ve done something wrong with the design – I’ve coded something that needs to act generically, but without writing generic code.
You might be wondering how generics could possibly help in the example of
const volatile above. Well, it wouldn’t be sufficient for C to have generics like C# (although it would certainly help). C++ templates are half a solution, but they have many shortfalls, which I won’t go into right now.
What would such a type system look like? Well if there was an obvious answer to that then C would probably already have it. Computer science research is constantly improving the situation at a theoretical level, and I think soon we’ll have the perfect low-level type system, just in time to not need it anymore.